httpbinx 1.7.0__py3-none-any.whl → 1.8.0__py3-none-any.whl

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.
httpbinx/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from .main import app
2
2
 
3
- __version__ = '1.7.0'
3
+ __version__ = '1.8.0'
4
4
 
5
5
  app.version = __version__
httpbinx/constants.py CHANGED
@@ -1,5 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
- # flake8: noqa
3
1
  REDIRECT_LOCATION = '/redirect/1'
4
2
 
5
3
  ACCEPTED_MEDIA_TYPES = [
@@ -27,9 +25,9 @@ ASCII_ART = """
27
25
  _...._
28
26
  .' _ _ `.
29
27
  | ."` ^ `". _,
30
- \_;`"---"`|//
28
+ \\_;`"---"`|//
31
29
  | ;/
32
- \_ _/
30
+ \\_ _/
33
31
  `\"\"\"`
34
32
  """
35
33
 
@@ -40,7 +38,7 @@ ANGRY_ASCII = """
40
38
  : :
41
39
  | |
42
40
  : __ :
43
- \ .-"` `"-. /
41
+ \\ .-"` `"-. /
44
42
  '. .'
45
43
  '-......-'
46
44
  YOU SHOULDN'T BE HERE
httpbinx/helpers.py CHANGED
@@ -1,21 +1,23 @@
1
- # -*- coding: utf-8 -*-
2
1
  import json
3
2
  import random
4
3
  import re
4
+ from pathlib import Path
5
5
 
6
6
  from starlette import status
7
7
  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
12
- from httpbinx.constants import ASCII_ART
13
- from httpbinx.constants import REDIRECT_LOCATION
14
- from httpbinx.schemas import RequestAttrs
15
- from httpbinx.schemas import RequestInfo
11
+ from httpbinx.constants import (ACCEPTED_MEDIA_TYPES, ASCII_ART,
12
+ REDIRECT_LOCATION)
13
+ from httpbinx.schemas import RequestAttrs, RequestInfo
16
14
 
17
15
  # init Jinja2
18
16
  _templates = Jinja2Templates(directory='templates')
17
+ # image path
18
+ _images_path = Path(__file__).parent / 'static' / 'images'
19
+ # bomb files path
20
+ _bomb_files_path = Path(__file__).parent / 'static' / 'bombs'
19
21
 
20
22
 
21
23
  def get_templates() -> Jinja2Templates:
@@ -23,6 +25,20 @@ def get_templates() -> Jinja2Templates:
23
25
  return _templates
24
26
 
25
27
 
28
+ def get_images_path() -> Path:
29
+ """Dependency function that returns the path to the images directory"""
30
+ if not _images_path.exists():
31
+ raise FileNotFoundError(f'{_images_path} does not exist.')
32
+ return _images_path
33
+
34
+
35
+ def get_bomb_file_path() -> Path:
36
+ """Dependency function that returns the path to the bomb files directory"""
37
+ if not _bomb_files_path.exists():
38
+ raise FileNotFoundError(f'{_bomb_files_path} does not exist.')
39
+ return _bomb_files_path
40
+
41
+
26
42
  async def to_request_info(request: Request, **extras) -> RequestInfo:
27
43
  """Returns model RequestInfo instance"""
28
44
  await request.body() # Note: Execute `.stream()` only once.
httpbinx/main.py CHANGED
@@ -1,9 +1,9 @@
1
- from os import path
1
+ from pathlib import Path
2
2
 
3
3
  from fastapi import FastAPI
4
4
  from fastapi.staticfiles import StaticFiles
5
5
 
6
- from httpbinx.meta import tags_metadata
6
+ from httpbinx.meta import get_tags_metadata
7
7
  from httpbinx.routers import router
8
8
 
9
9
  app = FastAPI(
@@ -12,14 +12,15 @@ app = FastAPI(
12
12
  'written in Python + FastAPI.',
13
13
  docs_url='/', # swagger docs page url
14
14
  swagger_ui_parameters={'docExpansion': 'none'},
15
- openapi_tags=tags_metadata
15
+ openapi_tags=get_tags_metadata()
16
16
  )
17
17
 
18
+ # mount static files
19
+ static_dir = Path(__file__).parent / 'static'
18
20
  app.mount(
19
21
  '/static',
20
- StaticFiles(directory=path.join(path.dirname(__file__), 'static')),
22
+ StaticFiles(directory=static_dir),
21
23
  name='static'
22
24
  )
23
25
 
24
- # app.openapi_tags = []
25
26
  app.include_router(router=router)
httpbinx/meta.py CHANGED
@@ -1,45 +1,42 @@
1
- # -*- coding: utf-8 -*-
2
- tags_metadata = [
3
- {
4
- 'name': 'HTTP Methods',
5
- 'description': 'Testing different HTTP verbs',
6
- },
7
- {
8
- 'name': 'Auth',
9
- 'description': 'Auth methods'
10
- },
11
- {
12
- 'name': 'Status codes',
13
- 'description': 'Generates responses with given status code',
14
- },
15
- {
16
- 'name': 'Request inspection',
17
- 'description': 'Inspect the request data'
18
- },
19
- {
20
- 'name': 'Response inspection',
21
- 'description': 'Inspect the response data like caching and headers',
22
- },
23
- {
24
- 'name': 'Response formats',
25
- 'description': 'Returns responses in different data formats',
26
- },
27
- {
28
- 'name': 'Dynamic data',
29
- 'description': 'Generates random and dynamic data'
30
- },
31
- {
32
- 'name': 'Cookies',
33
- 'description': 'Creates, reads and deletes Cookies'
34
- },
35
- {
36
- 'name': 'Images',
37
- 'description': 'Returns different image formats'},
38
- {
39
- 'name': 'Redirects',
40
- 'description': 'Returns different redirect responses'},
41
- {
42
- 'name': 'Anything',
43
- 'description': 'Returns anything that is passed to request',
44
- }
45
- ]
1
+ """
2
+ This module defines the metadata for API tags used in the FastAPI application.
3
+ These tags are used to organize and categorize different endpoints in the API documentation.
4
+ """
5
+ from functools import lru_cache
6
+ from typing import List
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class Tag(BaseModel):
12
+ name: str = Field(..., description='The name of the tag')
13
+ description: str = Field(..., description='A brief description of the tag')
14
+
15
+
16
+ class TagsMetadata(BaseModel):
17
+ tags: List[Tag] = Field(..., description='List of API tags')
18
+
19
+
20
+ TAGS_METADATA = TagsMetadata(
21
+ tags=[
22
+ Tag(name='HTTP Methods', description='Testing different HTTP verbs'),
23
+ Tag(name='Auth', description='Auth methods'),
24
+ Tag(name='Status codes', description='Generates responses with given status code'),
25
+ Tag(name='Request inspection', description='Inspect the request data'),
26
+ Tag(name='Response inspection', description='Inspect the response data like caching and headers'),
27
+ Tag(name='Response formats', description='Returns responses in different data formats'),
28
+ Tag(name='Dynamic data', description='Generates random and dynamic data'),
29
+ Tag(name='Cookies', description='Creates, reads and deletes Cookies'),
30
+ Tag(name='Images', description='Returns different image formats'),
31
+ Tag(name='Redirects', description='Returns different redirect responses'),
32
+ Tag(name='Anything', description='Returns anything that is passed to request'),
33
+ ]
34
+ )
35
+
36
+
37
+ @lru_cache
38
+ def get_tags_metadata() -> List[dict]:
39
+ """
40
+ Returns the tags metadata in the format expected by FastAPI.
41
+ """
42
+ return [tag.model_dump() for tag in TAGS_METADATA.tags]
@@ -1,13 +1,25 @@
1
- # -*- coding: utf-8 -*-
2
- from fastapi import APIRouter
1
+ from enum import Enum
2
+ from os import path
3
+
4
+ from fastapi import APIRouter, Path
3
5
  from starlette.requests import Request
6
+ from starlette.responses import FileResponse
4
7
 
5
- from httpbinx.helpers import to_request_info
8
+ from httpbinx.helpers import get_bomb_file_path, to_request_info
6
9
  from httpbinx.schemas import RequestInfo
7
10
 
8
11
  router = APIRouter(tags=['Anything'])
9
12
 
10
13
 
14
+ class BombTypes(str, Enum):
15
+ """The allowed bomb file compression types are: brotli and gzip."""
16
+ brotli = 'brotli'
17
+ gzip = 'gzip'
18
+
19
+
20
+ bombs_path: Path = get_bomb_file_path()
21
+
22
+
11
23
  @router.api_route(
12
24
  '/anything', # TODO path regex
13
25
  response_model=RequestInfo,
@@ -17,3 +29,34 @@ router = APIRouter(tags=['Anything'])
17
29
  )
18
30
  async def anything(request: Request):
19
31
  return await to_request_info(request)
32
+
33
+
34
+ @router.get(
35
+ '/bombs/{file}',
36
+ response_class=FileResponse,
37
+ summary='Returns a bomb file.',
38
+ 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.'
41
+ )
42
+ async def bomb_file(
43
+ *,
44
+ file: BombTypes = Path(
45
+ ...,
46
+ title='Compression types',
47
+ description='The allowed bomb file compression types',
48
+ )
49
+ ):
50
+ if file == BombTypes.gzip:
51
+ # dd if=/dev/zero bs=1M count=1000 | gzip -c > bomb-1GB.gz
52
+ return FileResponse(
53
+ path=path.join(bombs_path, 'bomb-1GB.gz'),
54
+ headers={'Content-Encoding': 'gzip'},
55
+ )
56
+ elif file == BombTypes.brotli:
57
+ # dd if=/dev/zero bs=1M count=1000 | brotli > bomb-1GB.br
58
+ return FileResponse(
59
+ path=path.join(bombs_path, 'bomb-1GB.br'),
60
+ headers={'Content-Encoding': 'br'},
61
+ )
62
+ raise ValueError(f'{file} is not a valid file type.')
@@ -1,21 +1,20 @@
1
- # -*- coding: utf-8 -*-
2
1
  """Images"""
3
- from os import path
2
+ from pathlib import Path
4
3
 
5
4
  from fastapi import APIRouter
6
5
  from starlette.requests import Request
7
6
  from starlette.responses import FileResponse
8
7
  from starlette.status import HTTP_406_NOT_ACCEPTABLE
9
8
 
10
- from httpbinx.helpers import status_code_response
9
+ from httpbinx.helpers import get_images_path, status_code_response
11
10
 
12
11
 
13
12
  class ImageResponse(FileResponse):
14
13
  """set response headers Content-Type: image/* """
15
- media_type = 'image/*'
14
+ media_type = 'image/*' # default
16
15
 
17
16
 
18
- images_path = path.join('static', 'images')
17
+ images_path: Path = get_images_path()
19
18
 
20
19
  router = APIRouter(
21
20
  tags=['Images'],
@@ -32,9 +31,6 @@ router = APIRouter(
32
31
  )
33
32
  async def image(request: Request):
34
33
  accept = request.headers.get('accept')
35
- if not accept:
36
- # Default media type to png
37
- return await image_png()
38
34
  accept = accept.lower()
39
35
  if 'image/webp' in accept:
40
36
  return await image_webp()
@@ -42,7 +38,8 @@ async def image(request: Request):
42
38
  return await image_svg()
43
39
  elif 'image/jpeg' in accept:
44
40
  return await image_jpeg()
45
- elif 'image/png' in accept or 'image/*' in accept:
41
+ elif any(x in accept for x in ['image/png', 'image/*', '*/*']):
42
+ # Default media type to png
46
43
  return await image_png()
47
44
  else:
48
45
  return status_code_response(HTTP_406_NOT_ACCEPTABLE)
@@ -55,7 +52,7 @@ async def image(request: Request):
55
52
  response_description='A PNG image.'
56
53
  )
57
54
  async def image_png():
58
- return ImageResponse(path=path.join(images_path, 'pig_icon.png'))
55
+ return ImageResponse(path=images_path / 'pig_icon.png')
59
56
 
60
57
 
61
58
  @router.get(
@@ -65,7 +62,7 @@ async def image_png():
65
62
  response_description='A JPEG image.'
66
63
  )
67
64
  async def image_jpeg():
68
- return ImageResponse(path=path.join(images_path, 'jackal.jpg'))
65
+ return ImageResponse(path=images_path / 'jackal.jpg')
69
66
 
70
67
 
71
68
  @router.get(
@@ -75,7 +72,7 @@ async def image_jpeg():
75
72
  response_description='A WEBP image.'
76
73
  )
77
74
  async def image_webp():
78
- return ImageResponse(path=path.join(images_path, 'wolf_1.webp'))
75
+ return ImageResponse(path=images_path / 'wolf_1.webp')
79
76
 
80
77
 
81
78
  @router.get(
@@ -85,4 +82,4 @@ async def image_webp():
85
82
  response_description='An SVG image.'
86
83
  )
87
84
  async def image_svg():
88
- return ImageResponse(path=path.join(images_path, 'svg_logo.svg'))
85
+ return ImageResponse(path=images_path / 'svg_logo.svg')
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: httpbinx
3
- Version: 1.7.0
3
+ Version: 1.8.0
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>
@@ -54,13 +54,17 @@ Description-Content-Type: text/markdown
54
54
  ![![cover](httpbinx/static/images/httpbinx_cover.png)](https://raw.githubusercontent.com/imleowoo/httpbinx/main/httpbinx/static/images/httpbinx_cover.png)
55
55
 
56
56
  [![thanks](https://img.shields.io/badge/thanks-httpbin-green)](https://github.com/postmanlabs/httpbin)
57
- ![python](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)
57
+ ![python](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue)
58
58
 
59
59
  # httpbinx
60
60
 
61
61
  HTTP Request & Response Service, written in Python + FastAPI.
62
62
 
63
- ## Reference project
63
+ ## Deployed at:
64
+
65
+ - **https://httpbinx.wooe.cc**
66
+
67
+ ## Reference Project
64
68
 
65
69
  A [Kenneth Reitz](http://kennethreitz.org/bitcoin) Project. See https://github.com/postmanlabs/httpbin
66
70
 
@@ -1,21 +1,21 @@
1
- httpbinx-1.7.0.dist-info/METADATA,sha256=lq16eYKPDfvYVcUQjmKsbJGAxVZ-Oz9yLTew3P9OAww,3730
2
- httpbinx-1.7.0.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- httpbinx-1.7.0.dist-info/entry_points.txt,sha256=sRkqxXJlZzL_lyEEQOecT_wue4JP64lYhpLwrPCZ-lI,66
4
- httpbinx-1.7.0.dist-info/licenses/LICENSE,sha256=t6oZPzEcm1CsBu9sB_8JYTc5rSOUhyQUee5qvAg4o-A,1060
5
- httpbinx/__init__.py,sha256=r5PcxwlTsHcKSegPCbofDdMLXduzUb47_kzegLrOm5w,72
1
+ httpbinx-1.8.0.dist-info/METADATA,sha256=4ssX6hl1ANVFBLDBariGV7nvlc_q-Q55p9wWnZc7QVY,3780
2
+ httpbinx-1.8.0.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
+ httpbinx-1.8.0.dist-info/entry_points.txt,sha256=sRkqxXJlZzL_lyEEQOecT_wue4JP64lYhpLwrPCZ-lI,66
4
+ httpbinx-1.8.0.dist-info/licenses/LICENSE,sha256=t6oZPzEcm1CsBu9sB_8JYTc5rSOUhyQUee5qvAg4o-A,1060
5
+ httpbinx/__init__.py,sha256=BtFN03iQf37ecI8AikkuZ6MbBZuBEEcONvB73NCN2Hg,72
6
6
  httpbinx/cli.py,sha256=bMPWV5HbOzqNJr3HBqFsZkaFqLI5YJneQQpAzF66mOg,1417
7
- httpbinx/constants.py,sha256=aJay1yI313fLxfFXgBPLE6p_UABlHq1KGEcdO7Nf2FQ,906
7
+ httpbinx/constants.py,sha256=kXeT_Yzl_-4SJT_a7RxU5dfXBTe4-b1Zg8TBF-8AGbs,870
8
8
  httpbinx/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- httpbinx/helpers.py,sha256=-fJyFbToQ0QwRY5UZ3Bkx7Ui_nj_bk8Wd4qKRtYGIsg,3787
10
- httpbinx/main.py,sha256=OmXi1FgApFBxNISDv4m5DU2hKAPu7kGEjZcQ5AxdAJE,614
11
- httpbinx/meta.py,sha256=gqWHplgb0SiUFVmZ8-gbOrZA468R-y9uDCQjqeQSHN8,1185
9
+ httpbinx/helpers.py,sha256=VVwjLpEKxi_awEPqbgb8tfWnhJEHSQFTsthE0pBKgp4,4373
10
+ httpbinx/main.py,sha256=kq8pcewugwiInkB8Cf0MvyAKJaK9uoMEeBHB5_xaprk,639
11
+ httpbinx/meta.py,sha256=h6scAO050bHFiy7UG2Q8MsyJsAHWMsZAFmZntdUwTAs,1699
12
12
  httpbinx/routers/__init__.py,sha256=xwbZGA3-sV07HCPAly8o5uUJGbdKH3pBHUakXEjvRK4,1409
13
- httpbinx/routers/anything.py,sha256=KtNMHx4aEGQS6fc21YdHszorm1_gPIfpYuCkImgQFCA,564
13
+ httpbinx/routers/anything.py,sha256=_FK1D9xEONlVIF_rvlqe_tBSsmG7ba8K_m-kGFotWWg,1923
14
14
  httpbinx/routers/auth.py,sha256=nGZA1aQtrS2B74vM2NORHSWMXf5q25JC2J63hAO2_Us,717
15
15
  httpbinx/routers/cookies.py,sha256=TB3ADXXg0kgZX5nE1QlrVk1mKXjdFAMJ7iMfd8kfMBc,2341
16
16
  httpbinx/routers/dynamicdata.py,sha256=Cas2JWkQnCaKNuE0Vw84h4YKcfl8PlsvAy6kYdvw_f4,6790
17
17
  httpbinx/routers/httpmethods.py,sha256=X9YPADi9D6FQnybJWyXc5BTDErgFZ5Xa4OIy7OKAifQ,1531
18
- httpbinx/routers/images.py,sha256=JBZ08Ez2gmXiGfA-jNXN3SaQzvQMmFkRrX9E0f_k3fk,2261
18
+ httpbinx/routers/images.py,sha256=d38pKQMinCbnipaBmE0gBlYar8Kog0ZOQEaaLregLZ8,2185
19
19
  httpbinx/routers/inspection/__init__.py,sha256=OMPci9p6uK9L6uFKQKbIqOn-r5MndCWSqlw-bJsUE3E,166
20
20
  httpbinx/routers/inspection/request.py,sha256=8b5HEuX2ED-YjMB5S0h-2xZDAmyo4ru9RX2S1tCwaqs,1224
21
21
  httpbinx/routers/inspection/response.py,sha256=yJ46k1O80WO8hq4gF09KHrsQJeP7Cn0aj6AAphfyyAY,2863
@@ -24,6 +24,8 @@ httpbinx/routers/responseformats.py,sha256=sqRLzSFjUmN5B9urbMVR7X_AydqvuT1pvHGIX
24
24
  httpbinx/routers/statuscodes.py,sha256=Iu0dcGsXLv34otIrH6WrWm_vOHt-9zAnblojM-d_qV0,1740
25
25
  httpbinx/schemas.py,sha256=QxBcsd4Go1azLPWF8E_uLyUVcnzI7Rwny-woDPvAzp0,4029
26
26
  httpbinx/static/UTF-8-demo.txt,sha256=rtAgU6uoYvQ0w7ij9JbkKdw6c3q-RchO1bAmrVzhksw,14058
27
+ httpbinx/static/bombs/bomb-1GB.br,sha256=pRVe549TQ4bzSP231INlng3sL_cGaDY6FQdaF7XtYKY,828
28
+ httpbinx/static/bombs/bomb-1GB.gz,sha256=9k1T0WSAGsXMi3e7kH-v_U8bZHMF3sgb4jOYroJ4ymY,1019197
27
29
  httpbinx/static/favicon.png,sha256=EsETjrMhmZy6X3LgRmOr_nu6dMWYGPGQ4XH3ESebrco,1291
28
30
  httpbinx/static/images/httbinx_logo.png,sha256=wqrQoDwsMvFgXtXeHfmtTqFrnsTA94N_esLo-5yfF58,3893
29
31
  httpbinx/static/images/httpbinx_cover.png,sha256=S8UtWBtwrfOUJTxyB1KsF9BFIh2UaRI3MEaZYemgLdU,2257
@@ -34,5 +36,4 @@ httpbinx/static/images/wolf_1.webp,sha256=Vnz6-U668nnOpOsLwFxGVQIftO4ASspSwJZwnT
34
36
  httpbinx/templates/moby.html,sha256=o519fgNb4vga6kx6ARW3MYpV7xrRJFzmyrOZwUmrYw8,3962
35
37
  httpbinx/templates/sample.xml,sha256=K12JbH36KNaAaBLkfyOhsUeXc-GfLHluHffGY3l2-V0,550
36
38
  httpbinx/templates/trackingscripts.html,sha256=3E-Lg6NpBpF74H3srJuswtHfD87gNEsuVxsjm9cqIYM,625
37
- httpbinx/utils.py,sha256=kMVB5ORarUJj6MdySbb8utCbgyM2WbitSmiDYBAFnSs,464
38
- httpbinx-1.7.0.dist-info/RECORD,,
39
+ httpbinx-1.8.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: pdm-backend (2.4.3)
2
+ Generator: pdm-backend (2.4.4)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
httpbinx/utils.py DELETED
@@ -1,15 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from functools import lru_cache
3
- from os import path
4
-
5
-
6
- @lru_cache(maxsize=128)
7
- def get_templates_abspath(subdir: str = None) -> str:
8
- """Return templates' folder abspath
9
- TODO use Jinja2Templates
10
- """
11
- # os.path.join(os.getcwd(), 'httpbin', 'templates')
12
- templates_abspath = path.join(path.dirname(__file__), 'templates')
13
- if subdir is None:
14
- return templates_abspath
15
- return path.join(templates_abspath, subdir)