codex-lb 0.5.0__py3-none-any.whl → 0.5.1__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.
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import gzip
3
4
  import io
5
+ import zlib
4
6
  from collections.abc import Awaitable, Callable
5
7
  from typing import Protocol
6
8
 
@@ -37,6 +39,67 @@ def _read_limited(reader: _Readable, max_size: int) -> bytes:
37
39
  return bytes(buffer)
38
40
 
39
41
 
42
+ def _decompress_gzip(data: bytes, max_size: int) -> bytes:
43
+ with gzip.GzipFile(fileobj=io.BytesIO(data)) as reader:
44
+ return _read_limited(reader, max_size)
45
+
46
+
47
+ def _decompress_deflate(data: bytes, max_size: int) -> bytes:
48
+ decompressor = zlib.decompressobj()
49
+ buffer = bytearray()
50
+ chunk_size = 64 * 1024
51
+ for start in range(0, len(data), chunk_size):
52
+ chunk = data[start : start + chunk_size]
53
+ # Bound output growth to avoid oversized allocations.
54
+ while chunk:
55
+ remaining = max_size - len(buffer)
56
+ if remaining == 0:
57
+ raise _DecompressedBodyTooLarge(max_size)
58
+ buffer.extend(decompressor.decompress(chunk, max_length=remaining))
59
+ chunk = decompressor.unconsumed_tail
60
+ while True:
61
+ remaining = max_size - len(buffer)
62
+ if remaining == 0:
63
+ raise _DecompressedBodyTooLarge(max_size)
64
+ drained = decompressor.decompress(b"", max_length=remaining)
65
+ if not drained:
66
+ break
67
+ buffer.extend(drained)
68
+ if not decompressor.eof:
69
+ raise zlib.error("Incomplete deflate stream")
70
+ return bytes(buffer)
71
+
72
+
73
+ def _decompress_zstd(data: bytes, max_size: int) -> bytes:
74
+ try:
75
+ decompressed = zstd.ZstdDecompressor().decompress(data, max_output_size=max_size)
76
+ if len(decompressed) > max_size:
77
+ raise _DecompressedBodyTooLarge(max_size)
78
+ return decompressed
79
+ except _DecompressedBodyTooLarge:
80
+ raise
81
+ except Exception:
82
+ with zstd.ZstdDecompressor().stream_reader(io.BytesIO(data)) as reader:
83
+ return _read_limited(reader, max_size)
84
+
85
+
86
+ def _decompress_body(data: bytes, encodings: list[str], max_size: int) -> bytes:
87
+ supported = {"zstd", "gzip", "deflate", "identity"}
88
+ if any(encoding not in supported for encoding in encodings):
89
+ raise ValueError("Unsupported content-encoding")
90
+ result = data
91
+ for encoding in reversed(encodings):
92
+ if encoding == "zstd":
93
+ result = _decompress_zstd(result, max_size)
94
+ elif encoding == "gzip":
95
+ result = _decompress_gzip(result, max_size)
96
+ elif encoding == "deflate":
97
+ result = _decompress_deflate(result, max_size)
98
+ elif encoding == "identity":
99
+ continue
100
+ return result
101
+
102
+
40
103
  def _replace_request_body(request: Request, body: bytes) -> None:
41
104
  request._body = body
42
105
  headers: list[tuple[bytes, bytes]] = []
@@ -60,15 +123,13 @@ def add_request_decompression_middleware(app: FastAPI) -> None:
60
123
  if not content_encoding:
61
124
  return await call_next(request)
62
125
  encodings = [enc.strip().lower() for enc in content_encoding.split(",") if enc.strip()]
63
- if encodings != ["zstd"]:
126
+ if not encodings:
64
127
  return await call_next(request)
65
128
  body = await request.body()
66
129
  settings = get_settings()
67
130
  max_size = settings.max_decompressed_body_bytes
68
131
  try:
69
- decompressed = zstd.ZstdDecompressor().decompress(body, max_output_size=max_size)
70
- if len(decompressed) > max_size:
71
- raise _DecompressedBodyTooLarge(max_size)
132
+ decompressed = _decompress_body(body, encodings, max_size)
72
133
  except _DecompressedBodyTooLarge:
73
134
  return JSONResponse(
74
135
  status_code=413,
@@ -77,25 +138,21 @@ def add_request_decompression_middleware(app: FastAPI) -> None:
77
138
  "Request body exceeds the maximum allowed size",
78
139
  ),
79
140
  )
141
+ except ValueError:
142
+ return JSONResponse(
143
+ status_code=400,
144
+ content=dashboard_error(
145
+ "invalid_request",
146
+ "Unsupported Content-Encoding",
147
+ ),
148
+ )
80
149
  except Exception:
81
- try:
82
- with zstd.ZstdDecompressor().stream_reader(io.BytesIO(body)) as reader:
83
- decompressed = _read_limited(reader, max_size)
84
- except _DecompressedBodyTooLarge:
85
- return JSONResponse(
86
- status_code=413,
87
- content=dashboard_error(
88
- "payload_too_large",
89
- "Request body exceeds the maximum allowed size",
90
- ),
91
- )
92
- except Exception:
93
- return JSONResponse(
94
- status_code=400,
95
- content=dashboard_error(
96
- "invalid_request",
97
- "Request body is zstd-compressed but could not be decompressed",
98
- ),
99
- )
150
+ return JSONResponse(
151
+ status_code=400,
152
+ content=dashboard_error(
153
+ "invalid_request",
154
+ "Request body is compressed but could not be decompressed",
155
+ ),
156
+ )
100
157
  _replace_request_body(request, decompressed)
101
158
  return await call_next(request)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-lb
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Codex load balancer and proxy for ChatGPT accounts with usage dashboard
5
5
  Author-email: Soju06 <qlskssk@gmail.com>
6
6
  Maintainer-email: Soju06 <qlskssk@gmail.com>
@@ -161,6 +161,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
161
161
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Quack6765"><img src="https://avatars.githubusercontent.com/u/5446230?v=4?s=100" width="100px;" alt="Quack"/><br /><sub><b>Quack</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=Quack6765" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/issues?q=author%3AQuack6765" title="Bug reports">🐛</a> <a href="#maintenance-Quack6765" title="Maintenance">🚧</a> <a href="#design-Quack6765" title="Design">🎨</a></td>
162
162
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/hhsw2015"><img src="https://avatars.githubusercontent.com/u/103614420?v=4?s=100" width="100px;" alt="Jill Kok, San Mou"/><br /><sub><b>Jill Kok, San Mou</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=hhsw2015" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/commits?author=hhsw2015" title="Tests">⚠️</a> <a href="#maintenance-hhsw2015" title="Maintenance">🚧</a></td>
163
163
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/pcy06"><img src="https://avatars.githubusercontent.com/u/44970486?v=4?s=100" width="100px;" alt="PARK CHANYOUNG"/><br /><sub><b>PARK CHANYOUNG</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=pcy06" title="Documentation">📖</a></td>
164
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/choi138"><img src="https://avatars.githubusercontent.com/u/84369321?v=4?s=100" width="100px;" alt="Choi138"/><br /><sub><b>Choi138</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=choi138" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/issues?q=author%3Achoi138" title="Bug reports">🐛</a> <a href="https://github.com/Soju06/codex-lb/commits?author=choi138" title="Tests">⚠️</a></td>
164
165
  </tr>
165
166
  </tbody>
166
167
  </table>
@@ -24,7 +24,7 @@ app/core/handlers/__init__.py,sha256=iyLUtPdBMYjK0mGpyRZ8hII8hiLF5jLR2jHiSI0C2zU
24
24
  app/core/handlers/exceptions.py,sha256=edNaaPSPweBUSIpBUjV3GWKVTQkARngQV4Oe32p32tI,1453
25
25
  app/core/middleware/__init__.py,sha256=3u3ibH0TTkvMjpN7tDu2phDBdTsssSFsyDJPAhq0Um0,372
26
26
  app/core/middleware/api_errors.py,sha256=W5FpmbWiQ2rGfNCOHUums_Es7Qz7ktzCNONdj27k2Ps,1046
27
- app/core/middleware/request_decompression.py,sha256=PwqibE0pwyZW6o2KrpNWcZhyVs07rYEQTY8xEwFoGYo,3667
27
+ app/core/middleware/request_decompression.py,sha256=8qwpXVWlLGijhtLMBWfcq9sRrKwEHI3wxqah0BghNFY,5572
28
28
  app/core/middleware/request_id.py,sha256=f2CMCTy9HbHM_QfWs-EvqIAOHxLhuuC5Sd13ecUKrpc,924
29
29
  app/core/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  app/core/openai/chat_requests.py,sha256=M1Mfu4_WNb5f5PXg4-SMjMWGtI1gkhweIfa1ENANz3k,6787
@@ -101,8 +101,8 @@ app/modules/usage/updater.py,sha256=TUFLIjIe8c0m06bnO6iumzAyFuMED3B1ry3Ty5o8JRo,
101
101
  app/static/index.css,sha256=4EcLWTHAkhbqldi9fE1Y_bQCtmJuXUgEs0JHV-KmV9w,31757
102
102
  app/static/index.html,sha256=g1U2AlbvGChev5KIyPoVI5TJ3dnvEOqoAot1tENvQOs,39799
103
103
  app/static/index.js,sha256=q-IXWQURYksd-i68yXwyqBazs-AYiz3Tvk5t_B6NbSY,69750
104
- codex_lb-0.5.0.dist-info/METADATA,sha256=ym9TYcpUCgJ66jUQX9uorIxzYr-BFSHwOhXfAW6Vyzc,7338
105
- codex_lb-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
106
- codex_lb-0.5.0.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
107
- codex_lb-0.5.0.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
108
- codex_lb-0.5.0.dist-info/RECORD,,
104
+ codex_lb-0.5.1.dist-info/METADATA,sha256=XuzL3i-rYS2lIxLr9XJ_XzHN7mcQSyF2qFBBoofJSew,7857
105
+ codex_lb-0.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
106
+ codex_lb-0.5.1.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
107
+ codex_lb-0.5.1.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
108
+ codex_lb-0.5.1.dist-info/RECORD,,