renderers 0.1.9.dev1__tar.gz → 0.1.9.dev3__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.
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/PKG-INFO +1 -1
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/_version.py +2 -2
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/configs.py +15 -2
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/parsing.py +19 -6
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/qwen3.py +20 -1
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/qwen35.py +20 -3
- renderers-0.1.9.dev3/tests/test_disabled_thinking_stability.py +226 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_multimodal.py +20 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_prime_qwen3_parity.py +58 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_renderer_config_parity.py +25 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/uv.lock +49 -70
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/.github/workflows/style.yml +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/.github/workflows/test.yml +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/.gitignore +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/LICENSE +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/README.md +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/docs/renderer-config.md +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/examples/README.md +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/pyproject.toml +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/__init__.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/base.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/client.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/deepseek_r1.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/deepseek_v3.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/default.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/glm45.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/glm5.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/gpt_oss.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/hy3.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/kimi_k2.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/kimi_k25.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/laguna_xs2.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/llama_3.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/minimax_m2.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/nemotron3.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/parsers.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/prime_qwen3.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/qwen36.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/renderers/qwen3_vl.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/conftest.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_bridge.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_build_helpers.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_client.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_deepseek_r1.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_gpt_oss_harmony_parity.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_hy3.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_incremental.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_is_content.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_laguna_xs21.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_llama_3.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_message_indices.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_message_tool_names.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_nemotron3_parity.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_nemotron3_ultra.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_parse_response.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_parsers.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_render_ids.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_renderer_config.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_roundtrip.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.9.dev1 → renderers-0.1.9.dev3}/tests/test_tool_arg_type_preservation.py +0 -0
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.9.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 9, '
|
|
21
|
+
__version__ = version = '0.1.9.dev3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 9, 'dev3')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -178,7 +178,13 @@ class Qwen3RendererConfig(BaseRendererConfig):
|
|
|
178
178
|
enable_thinking: bool = True
|
|
179
179
|
"""When ``True``, the generation prompt includes ``<think>`` so the
|
|
180
180
|
model continues into a thinking block. Mirrors the chat template's
|
|
181
|
-
``enable_thinking`` kwarg.
|
|
181
|
+
``enable_thinking`` kwarg.
|
|
182
|
+
|
|
183
|
+
When ``False``, the renderer deliberately deviates from the template
|
|
184
|
+
on historical assistant turns without ``reasoning_content``: the empty
|
|
185
|
+
``<think>\\n\\n</think>\\n\\n`` wrapper the generation prompt prefilled
|
|
186
|
+
is re-emitted instead of stripped, keeping re-renders token-stable with
|
|
187
|
+
the sampled stream (see ``renderers/qwen3.py`` module docstring)."""
|
|
182
188
|
|
|
183
189
|
|
|
184
190
|
class PrimeQwen3RendererConfig(BaseRendererConfig):
|
|
@@ -196,7 +202,14 @@ class Qwen35RendererConfig(BaseRendererConfig):
|
|
|
196
202
|
"""When ``True``, the generation prompt includes ``<think>``. ``None``
|
|
197
203
|
auto-detects from the tokenizer's chat-template default — Instruct
|
|
198
204
|
checkpoints default off, Thinking checkpoints default on. Mirrors
|
|
199
|
-
the chat template's ``enable_thinking`` kwarg.
|
|
205
|
+
the chat template's ``enable_thinking`` kwarg.
|
|
206
|
+
|
|
207
|
+
When resolved ``False``, the renderer deliberately deviates from the
|
|
208
|
+
template on historical assistant turns without ``reasoning_content``:
|
|
209
|
+
the empty ``<think>\\n\\n</think>\\n\\n`` wrapper the generation prompt
|
|
210
|
+
prefilled is re-emitted instead of stripped, keeping re-renders
|
|
211
|
+
token-stable with the sampled stream (see ``renderers/qwen35.py``
|
|
212
|
+
module docstring)."""
|
|
200
213
|
|
|
201
214
|
add_vision_id: bool = False
|
|
202
215
|
"""When ``True``, prefix each ``<|vision_start|>`` placeholder with
|
|
@@ -286,7 +286,15 @@ def parse_qwen35(
|
|
|
286
286
|
tool_call_end_id: int,
|
|
287
287
|
tools: list[ToolSpec] | None = None,
|
|
288
288
|
) -> ParsedResponse:
|
|
289
|
-
"""Parse Qwen3.5 completion tokens. XML-style tool calls, token-level thinking.
|
|
289
|
+
"""Parse Qwen3.5 completion tokens. XML-style tool calls, token-level thinking.
|
|
290
|
+
|
|
291
|
+
``reasoning_content`` contract: ``None`` means the completion carried no
|
|
292
|
+
think block at all; a string (possibly empty) means a think block was
|
|
293
|
+
present. The distinction matters for renderers with key-presence
|
|
294
|
+
semantics (PrimeQwen3Renderer): a sampled ``<think></think>`` must
|
|
295
|
+
round-trip parse → message → render back to the same tokens, so an
|
|
296
|
+
empty-but-present block is ``""``, never ``None``.
|
|
297
|
+
"""
|
|
290
298
|
ids = _strip_stop_tokens(token_ids, stop_ids)
|
|
291
299
|
|
|
292
300
|
# Thinking: find </think> by token ID
|
|
@@ -300,12 +308,12 @@ def parse_qwen35(
|
|
|
300
308
|
ids = ids[think_end + 1 :]
|
|
301
309
|
parse_offset = think_end + 1
|
|
302
310
|
elif think_id in set(ids):
|
|
303
|
-
# <think> present but no </think> — truncated reasoning
|
|
311
|
+
# <think> present but no </think> — truncated reasoning. Block
|
|
312
|
+
# present ⇒ string (see docstring), even when nothing follows the
|
|
313
|
+
# opening tag.
|
|
304
314
|
think_start = _find(ids, think_id)
|
|
305
315
|
reasoning = _decode(tokenizer, ids[think_start + 1 :]).strip()
|
|
306
|
-
return ParsedResponse(
|
|
307
|
-
content="", reasoning_content=reasoning or None, tool_calls=[]
|
|
308
|
-
)
|
|
316
|
+
return ParsedResponse(content="", reasoning_content=reasoning, tool_calls=[])
|
|
309
317
|
|
|
310
318
|
tc_start = _find(ids, tool_call_id)
|
|
311
319
|
tool_calls: list[ParsedToolCall] = []
|
|
@@ -322,9 +330,14 @@ def parse_qwen35(
|
|
|
322
330
|
else:
|
|
323
331
|
content_text = _decode(tokenizer, ids).strip()
|
|
324
332
|
|
|
333
|
+
# ``reasoning`` is None only when no think block was found; keep ""
|
|
334
|
+
# (empty-but-present block) intact so ``<think></think>`` samples
|
|
335
|
+
# round-trip — collapsing it to None made PrimeQwen3Renderer drop the
|
|
336
|
+
# block on re-render, forking the token stream for the rest of the
|
|
337
|
+
# rollout.
|
|
325
338
|
return ParsedResponse(
|
|
326
339
|
content=content_text,
|
|
327
|
-
reasoning_content=reasoning
|
|
340
|
+
reasoning_content=reasoning,
|
|
328
341
|
tool_calls=tool_calls,
|
|
329
342
|
)
|
|
330
343
|
|
|
@@ -5,6 +5,15 @@ Key differences from Qwen3.5:
|
|
|
5
5
|
- Tool calls use JSON format: {"name": "...", "arguments": ...}
|
|
6
6
|
- Thinking blocks only inserted when loop.last OR reasoning_content present
|
|
7
7
|
- Generation prompt does NOT add <think> by default
|
|
8
|
+
|
|
9
|
+
One documented deviation from the Jinja template: with
|
|
10
|
+
``enable_thinking=False`` the empty ``<think>\n\n</think>\n\n`` wrapper is
|
|
11
|
+
re-emitted on historical assistant turns without ``reasoning_content`` (the
|
|
12
|
+
template strips it from turns at or before the last user query). The
|
|
13
|
+
generation prompt prefills that wrapper, so every turn sampled with thinking
|
|
14
|
+
disabled contains it — stripping it on re-render would make the same
|
|
15
|
+
conversation produce different tokens depending on when it is rendered.
|
|
16
|
+
See ``tests/test_disabled_thinking_stability.py``.
|
|
8
17
|
"""
|
|
9
18
|
|
|
10
19
|
from __future__ import annotations
|
|
@@ -477,7 +486,17 @@ class Qwen3Renderer:
|
|
|
477
486
|
emit_in_template_window = msg_idx > last_query_index and (
|
|
478
487
|
is_last or reasoning_content
|
|
479
488
|
)
|
|
480
|
-
|
|
489
|
+
# With thinking disabled, the generation prompt prefilled the empty
|
|
490
|
+
# ``<think>\n\n</think>\n\n`` wrapper, so it is part of every sampled
|
|
491
|
+
# turn's token stream. Re-emit it on historical turns — deviating
|
|
492
|
+
# from the Jinja template, which strips it from turns at or before
|
|
493
|
+
# the last user query — so re-renders stay token-stable with what
|
|
494
|
+
# the model actually sampled. Turns that carry reasoning_content
|
|
495
|
+
# were not sampled under this config; those stay template-faithful.
|
|
496
|
+
emit_thinking = emit_in_template_window or (
|
|
497
|
+
not self.config.enable_thinking and not reasoning_content
|
|
498
|
+
)
|
|
499
|
+
if emit_thinking:
|
|
481
500
|
body = (
|
|
482
501
|
"<think>\n"
|
|
483
502
|
+ reasoning_content.strip("\n")
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
Produces token-for-token identical output to tokenizer.apply_chat_template() while
|
|
4
4
|
also tracking which message produced each token (for per-token loss masks).
|
|
5
5
|
|
|
6
|
+
One documented deviation: with ``enable_thinking=False`` the empty
|
|
7
|
+
``<think>\n\n</think>\n\n`` wrapper is re-emitted on historical assistant
|
|
8
|
+
turns without ``reasoning_content`` (the Jinja template strips it from turns
|
|
9
|
+
at or before the last user query). The generation prompt prefills that
|
|
10
|
+
wrapper, so every turn sampled with thinking disabled contains it — stripping
|
|
11
|
+
it on re-render would make the same conversation produce different tokens
|
|
12
|
+
depending on when it is rendered. See ``tests/test_disabled_thinking_stability.py``.
|
|
13
|
+
|
|
6
14
|
Multimodal: the Qwen3.5 family is itself a VLM (HF tag ``image-text-to-text``;
|
|
7
15
|
processor class ``Qwen3VLProcessor``). When a user/tool message carries an
|
|
8
16
|
``ImagePart``, the renderer emits the same ``<|vision_start|>``+N×``<|image_pad|>``
|
|
@@ -959,9 +967,18 @@ class Qwen35Renderer:
|
|
|
959
967
|
# call tags (``<function=...>``, ``<parameter=...>``, etc.) are
|
|
960
968
|
# part of the model's emitted output too — keep them
|
|
961
969
|
# ``is_content=True`` per the assistant rule.
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
970
|
+
# With thinking disabled, the generation prompt prefilled the empty
|
|
971
|
+
# ``<think>\n\n</think>\n\n`` wrapper, so it is part of every sampled
|
|
972
|
+
# turn's token stream. Re-emit it on historical turns — deviating
|
|
973
|
+
# from the Jinja template, which strips it from turns at or before
|
|
974
|
+
# the last user query — so re-renders stay token-stable with what
|
|
975
|
+
# the model actually sampled. Turns that carry reasoning_content
|
|
976
|
+
# were not sampled under this config; those stay template-faithful.
|
|
977
|
+
emit_thinking = (
|
|
978
|
+
self._should_render_thinking(msg_idx, last_query_index)
|
|
979
|
+
or getattr(self.config, "preserve_thinking", False)
|
|
980
|
+
or (not self.config.enable_thinking and not reasoning_content)
|
|
981
|
+
)
|
|
965
982
|
if emit_thinking:
|
|
966
983
|
# Include thinking block
|
|
967
984
|
emit_special(self._think, msg_idx, is_sampled=True, is_content=True)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Sampled-token stability with thinking disabled (Qwen family).
|
|
2
|
+
|
|
3
|
+
With ``enable_thinking=False`` the generation prompt prefills the empty
|
|
4
|
+
``<think>\\n\\n</think>\\n\\n`` wrapper, so the wrapper is part of every
|
|
5
|
+
sampled turn's token stream. In an agentic rollout the conversation keeps
|
|
6
|
+
growing (tool responses, budget / prune-reminder user messages, ...) and may
|
|
7
|
+
be re-rendered from messages. The upstream Jinja template strips the wrapper
|
|
8
|
+
from assistant turns at or before the last user query, so a re-render would
|
|
9
|
+
diverge token-for-token from the stream the model actually sampled — forking
|
|
10
|
+
any token-identity comparison on byte-identical messages.
|
|
11
|
+
|
|
12
|
+
These tests pin the renderer-side guarantee that fixes this: with thinking
|
|
13
|
+
disabled, historical assistant turns without ``reasoning_content`` re-emit
|
|
14
|
+
the exact gen-prompt wrapper, so the tokens a turn was sampled with are a
|
|
15
|
+
prefix of every later re-render of the grown conversation, and the bridge
|
|
16
|
+
and a full re-render agree.
|
|
17
|
+
|
|
18
|
+
This is a deliberate, documented deviation from ``apply_chat_template`` —
|
|
19
|
+
see the carve-out in ``test_renderer_config_parity.py`` and the module
|
|
20
|
+
docstrings of ``renderers/qwen3.py`` / ``renderers/qwen35.py``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from functools import lru_cache
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
from renderers import create_renderer
|
|
29
|
+
from renderers.base import load_tokenizer
|
|
30
|
+
from renderers.configs import (
|
|
31
|
+
Qwen3RendererConfig,
|
|
32
|
+
Qwen35RendererConfig,
|
|
33
|
+
Qwen36RendererConfig,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# One representative model per affected renderer family, each with
|
|
37
|
+
# thinking explicitly disabled.
|
|
38
|
+
_MODELS = [
|
|
39
|
+
("Qwen/Qwen3-8B", Qwen3RendererConfig(enable_thinking=False)),
|
|
40
|
+
("Qwen/Qwen3.5-9B", Qwen35RendererConfig(enable_thinking=False)),
|
|
41
|
+
("Qwen/Qwen3.6-35B-A3B", Qwen36RendererConfig(enable_thinking=False)),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
_EMPTY_WRAPPER = "<think>\n\n</think>\n\n"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@lru_cache(maxsize=None)
|
|
48
|
+
def _load(model_name: str):
|
|
49
|
+
return load_tokenizer(model_name)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def pytest_generate_tests(metafunc):
|
|
53
|
+
if "dt_model" in metafunc.fixturenames:
|
|
54
|
+
metafunc.parametrize("dt_model,dt_config", _MODELS, ids=[m for m, _ in _MODELS])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_sampled_stream_is_prefix_of_rerender(dt_model, dt_config):
|
|
58
|
+
"""The rollout invariant: every generation prompt (which ends with the
|
|
59
|
+
prefilled empty wrapper) stays a byte prefix of all later re-renders of
|
|
60
|
+
the grown conversation — even after new real user queries arrive."""
|
|
61
|
+
tok = _load(dt_model)
|
|
62
|
+
renderer = create_renderer(tok, dt_config)
|
|
63
|
+
|
|
64
|
+
convo: list[dict] = [
|
|
65
|
+
{"role": "system", "content": "You are helpful."},
|
|
66
|
+
{"role": "user", "content": "What is the capital of France?"},
|
|
67
|
+
]
|
|
68
|
+
prev = renderer.render_ids(convo, add_generation_prompt=True)
|
|
69
|
+
steps = [
|
|
70
|
+
(
|
|
71
|
+
{"role": "assistant", "content": "Paris."},
|
|
72
|
+
{"role": "user", "content": "You are running low on budget."},
|
|
73
|
+
),
|
|
74
|
+
(
|
|
75
|
+
{"role": "assistant", "content": "Understood."},
|
|
76
|
+
{"role": "user", "content": "Submit your final answer now."},
|
|
77
|
+
),
|
|
78
|
+
]
|
|
79
|
+
for assistant, user in steps:
|
|
80
|
+
convo += [assistant, user]
|
|
81
|
+
cur = renderer.render_ids(convo, add_generation_prompt=True)
|
|
82
|
+
assert cur[: len(prev)] == prev, (
|
|
83
|
+
f"{dt_model}: re-render after appending {assistant['content']!r} "
|
|
84
|
+
"diverged from the earlier generation prompt — historical turn "
|
|
85
|
+
"lost its prefilled empty think wrapper"
|
|
86
|
+
)
|
|
87
|
+
prev = cur
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_wrapper_reemitted_on_historical_turn(dt_model, dt_config):
|
|
91
|
+
"""A historical assistant turn (before the last real user query, no
|
|
92
|
+
reasoning_content) renders with the exact empty wrapper bytes the
|
|
93
|
+
generation prompt prefilled."""
|
|
94
|
+
tok = _load(dt_model)
|
|
95
|
+
renderer = create_renderer(tok, dt_config)
|
|
96
|
+
|
|
97
|
+
rendered = renderer.render(
|
|
98
|
+
[
|
|
99
|
+
{"role": "user", "content": "What is 2+2?"},
|
|
100
|
+
{"role": "assistant", "content": "4"},
|
|
101
|
+
{"role": "user", "content": "Now 3+3?"},
|
|
102
|
+
]
|
|
103
|
+
)
|
|
104
|
+
turn = [t for t, i in zip(rendered.token_ids, rendered.message_indices) if i == 1]
|
|
105
|
+
text = tok.decode(turn)
|
|
106
|
+
assert _EMPTY_WRAPPER in text, (
|
|
107
|
+
f"{dt_model}: historical assistant turn rendered without the "
|
|
108
|
+
f"prefilled empty think wrapper: {text!r}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_bridge_and_rerender_agree(dt_model, dt_config):
|
|
113
|
+
"""Extending a sampled turn via the bridge and re-rendering the same
|
|
114
|
+
conversation from messages must produce identical tokens — the property
|
|
115
|
+
whose violation shows up as token-level forks on byte-identical
|
|
116
|
+
trajectories."""
|
|
117
|
+
tok = _load(dt_model)
|
|
118
|
+
renderer = create_renderer(tok, dt_config)
|
|
119
|
+
|
|
120
|
+
msgs = [{"role": "user", "content": "What is the capital of France?"}]
|
|
121
|
+
prompt_ids = renderer.render_ids(msgs, add_generation_prompt=True)
|
|
122
|
+
# Simulate the sampled completion: content tokens then the stop token.
|
|
123
|
+
completion_ids = tok.encode("Paris.", add_special_tokens=False) + [
|
|
124
|
+
renderer.get_stop_token_ids()[0]
|
|
125
|
+
]
|
|
126
|
+
reminder = {"role": "user", "content": "You are running low on budget."}
|
|
127
|
+
|
|
128
|
+
bridged = renderer.bridge_to_next_turn(prompt_ids, completion_ids, [reminder])
|
|
129
|
+
assert bridged is not None, (
|
|
130
|
+
f"{dt_model}: bridge refused with thinking disabled (implied "
|
|
131
|
+
"thinking_retention='all' should allow it)"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
rerender = renderer.render_ids(
|
|
135
|
+
msgs + [{"role": "assistant", "content": "Paris."}, reminder],
|
|
136
|
+
add_generation_prompt=True,
|
|
137
|
+
)
|
|
138
|
+
assert bridged.token_ids == rerender, (
|
|
139
|
+
f"{dt_model}: bridge-extended tokens and full re-render disagree:\n"
|
|
140
|
+
f"bridge: {tok.decode(bridged.token_ids)!r}\n"
|
|
141
|
+
f"rerender: {tok.decode(rerender)!r}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_tool_cycle_rerender_stays_prefix_stable(dt_model, dt_config):
|
|
146
|
+
"""Tool-call turns (empty content, no reasoning) keep the prefilled
|
|
147
|
+
wrapper too. On qwen3 the template window additionally requires
|
|
148
|
+
``is_last or reasoning_content``, so without the deviation a mid-cycle
|
|
149
|
+
re-render would strip the wrapper from the tool-call turn even though
|
|
150
|
+
it sits after the last real user query."""
|
|
151
|
+
tok = _load(dt_model)
|
|
152
|
+
renderer = create_renderer(tok, dt_config)
|
|
153
|
+
tools = [
|
|
154
|
+
{
|
|
155
|
+
"type": "function",
|
|
156
|
+
"function": {
|
|
157
|
+
"name": "get_weather",
|
|
158
|
+
"description": "Get the current weather for a city",
|
|
159
|
+
"parameters": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"properties": {"city": {"type": "string"}},
|
|
162
|
+
"required": ["city"],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
msgs: list[dict] = [{"role": "user", "content": "Weather in Paris?"}]
|
|
169
|
+
prev = renderer.render_ids(msgs, tools=tools, add_generation_prompt=True)
|
|
170
|
+
|
|
171
|
+
msgs += [
|
|
172
|
+
{
|
|
173
|
+
"role": "assistant",
|
|
174
|
+
"content": "",
|
|
175
|
+
"tool_calls": [
|
|
176
|
+
{"function": {"name": "get_weather", "arguments": {"city": "Paris"}}}
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
{"role": "tool", "content": '{"temp": 20}'},
|
|
180
|
+
]
|
|
181
|
+
cur = renderer.render_ids(msgs, tools=tools, add_generation_prompt=True)
|
|
182
|
+
assert cur[: len(prev)] == prev, (
|
|
183
|
+
f"{dt_model}: tool-call turn lost its prefilled empty think wrapper "
|
|
184
|
+
"on re-render"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
msgs += [
|
|
188
|
+
{"role": "assistant", "content": "It is 20 degrees."},
|
|
189
|
+
{"role": "user", "content": "You are running low on budget."},
|
|
190
|
+
]
|
|
191
|
+
cur2 = renderer.render_ids(msgs, tools=tools, add_generation_prompt=True)
|
|
192
|
+
assert cur2[: len(cur)] == cur
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_historical_reasoning_stays_template_faithful(dt_model, dt_config):
|
|
196
|
+
"""Scope guard: turns that carry ``reasoning_content`` were not sampled
|
|
197
|
+
under ``enable_thinking=False``, so they keep exact template parity —
|
|
198
|
+
the deviation fires only for the empty wrapper."""
|
|
199
|
+
tok = _load(dt_model)
|
|
200
|
+
renderer = create_renderer(tok, dt_config)
|
|
201
|
+
|
|
202
|
+
msgs = [
|
|
203
|
+
{"role": "user", "content": "What is 2+2?"},
|
|
204
|
+
{
|
|
205
|
+
"role": "assistant",
|
|
206
|
+
"reasoning_content": "Adding small ints.",
|
|
207
|
+
"content": "4",
|
|
208
|
+
},
|
|
209
|
+
{"role": "user", "content": "Now 3+3?"},
|
|
210
|
+
{
|
|
211
|
+
"role": "assistant",
|
|
212
|
+
"reasoning_content": "Same idea.",
|
|
213
|
+
"content": "6",
|
|
214
|
+
},
|
|
215
|
+
]
|
|
216
|
+
ours = renderer.render_ids(msgs)
|
|
217
|
+
theirs = list(
|
|
218
|
+
tok.apply_chat_template(
|
|
219
|
+
msgs,
|
|
220
|
+
tokenize=True,
|
|
221
|
+
return_dict=False,
|
|
222
|
+
add_generation_prompt=False,
|
|
223
|
+
enable_thinking=False,
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
assert ours == theirs
|
|
@@ -437,6 +437,20 @@ def _build_tool_image_cases(make_part, image):
|
|
|
437
437
|
# ---------------------------------------------------------------------------
|
|
438
438
|
|
|
439
439
|
|
|
440
|
+
def _skip_for_disabled_thinking_deviation(renderer, case_id) -> bool:
|
|
441
|
+
"""With ``enable_thinking=False`` (the resolved default on the small
|
|
442
|
+
Qwen3.5 sizes) the Qwen family re-emits the empty think wrapper on
|
|
443
|
+
historical assistant turns without reasoning — a documented deviation
|
|
444
|
+
from the template for sampled-token stability (see
|
|
445
|
+
``test_disabled_thinking_stability.py``). Cases with an assistant turn
|
|
446
|
+
before the last user query therefore diverge from the processor oracle
|
|
447
|
+
by exactly those wrapper tokens; skip them for parity purposes."""
|
|
448
|
+
return getattr(renderer.config, "enable_thinking", True) is False and case_id in (
|
|
449
|
+
"multi_turn_two_images",
|
|
450
|
+
"multi_turn_tool_response_images",
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
|
|
440
454
|
def _supports_tool_message_images(renderer) -> bool:
|
|
441
455
|
"""True iff this renderer emits image placeholders inside tool-response
|
|
442
456
|
content. Renderers without the feature silently drop image parts in tool
|
|
@@ -466,6 +480,8 @@ def test_multimodal_byte_parity_vs_processor(mm_model_name, modality, tiny_image
|
|
|
466
480
|
|
|
467
481
|
for case in _build_cases(kit["make_part"], tiny_image):
|
|
468
482
|
messages, add_gp = case.values
|
|
483
|
+
if _skip_for_disabled_thinking_deviation(renderer, case.id):
|
|
484
|
+
continue
|
|
469
485
|
|
|
470
486
|
# Ours.
|
|
471
487
|
ours = renderer.render_ids(messages, add_generation_prompt=add_gp)
|
|
@@ -709,6 +725,8 @@ def test_tool_response_image_byte_parity(mm_model_name, modality, tiny_image):
|
|
|
709
725
|
|
|
710
726
|
for case in _build_tool_image_cases(kit["make_part"], tiny_image):
|
|
711
727
|
messages, add_gp = case.values
|
|
728
|
+
if _skip_for_disabled_thinking_deviation(renderer, case.id):
|
|
729
|
+
continue
|
|
712
730
|
ours = renderer.render_ids(messages, add_generation_prompt=add_gp)
|
|
713
731
|
theirs = kit["processor_input_ids"](processor, messages, add_gp)
|
|
714
732
|
assert ours == theirs, (
|
|
@@ -796,6 +814,8 @@ def test_add_vision_id_parity_vs_processor(
|
|
|
796
814
|
|
|
797
815
|
for case in _build_cases(kit["make_part"], tiny_image):
|
|
798
816
|
messages, add_gp = case.values
|
|
817
|
+
if _skip_for_disabled_thinking_deviation(renderer, case.id):
|
|
818
|
+
continue
|
|
799
819
|
ours = renderer.render_ids(messages, add_generation_prompt=add_gp)
|
|
800
820
|
theirs = _qwen_vl_processor_input_ids_with_kwargs(
|
|
801
821
|
processor, messages, add_gp, add_vision_id=add_vision_id
|
|
@@ -194,3 +194,61 @@ def test_prime_qwen3_populates_assistant_masks(model):
|
|
|
194
194
|
== len(rendered.is_content)
|
|
195
195
|
== len(rendered.token_ids)
|
|
196
196
|
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pytest.mark.parametrize("model", MODELS)
|
|
200
|
+
def test_prime_qwen3_empty_think_roundtrip(model):
|
|
201
|
+
"""A sampled empty ``<think></think>`` block round-trips
|
|
202
|
+
parse → message → re-render byte-identically with the bridged sampled
|
|
203
|
+
stream. ``parse_qwen35`` used to collapse the empty-but-present block
|
|
204
|
+
to ``reasoning_content=None``; the renderer's key-presence check then
|
|
205
|
+
dropped the block on re-render, forking every later re-render of the
|
|
206
|
+
rollout (the template keeps reasoning on ALL historical turns, and
|
|
207
|
+
renders ``reasoning_content=""`` as ``<think></think>``)."""
|
|
208
|
+
tokenizer, renderer = _load(model)
|
|
209
|
+
stop = renderer.get_stop_token_ids()[0]
|
|
210
|
+
|
|
211
|
+
prompt = [{"role": "user", "content": "Reverse abc"}]
|
|
212
|
+
prompt_ids = renderer.render_ids(prompt, add_generation_prompt=True)
|
|
213
|
+
# Tokenize the sampled turn in context so the completion carries the
|
|
214
|
+
# exact BPE merges the model would produce continuing the stream.
|
|
215
|
+
sampled = "<think></think>\ncba"
|
|
216
|
+
full = tokenizer.encode(
|
|
217
|
+
tokenizer.decode(prompt_ids) + sampled, add_special_tokens=False
|
|
218
|
+
)
|
|
219
|
+
assert full[: len(prompt_ids)] == prompt_ids
|
|
220
|
+
completion_ids = full[len(prompt_ids) :] + [stop]
|
|
221
|
+
|
|
222
|
+
parsed = renderer.parse_response(completion_ids)
|
|
223
|
+
assert parsed.reasoning_content == "" # present-but-empty, not None
|
|
224
|
+
assert parsed.content == "cba"
|
|
225
|
+
|
|
226
|
+
assistant = {"role": "assistant", "content": parsed.content}
|
|
227
|
+
if parsed.reasoning_content is not None:
|
|
228
|
+
assistant["reasoning_content"] = parsed.reasoning_content
|
|
229
|
+
reminder = {"role": "user", "content": "Now reverse def"}
|
|
230
|
+
|
|
231
|
+
bridged = renderer.bridge_to_next_turn(prompt_ids, completion_ids, [reminder])
|
|
232
|
+
assert bridged is not None
|
|
233
|
+
rerender = renderer.render_ids(
|
|
234
|
+
[*prompt, assistant, reminder], add_generation_prompt=True
|
|
235
|
+
)
|
|
236
|
+
assert bridged.token_ids == rerender, (
|
|
237
|
+
f"{model}: bridged sampled stream and re-render disagree:\n"
|
|
238
|
+
f"bridge: {tokenizer.decode(bridged.token_ids)!r}\n"
|
|
239
|
+
f"rerender: {tokenizer.decode(rerender)!r}"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@pytest.mark.parametrize("model", MODELS)
|
|
244
|
+
def test_prime_qwen3_no_think_parses_to_none(model):
|
|
245
|
+
"""Guard: a completion with no think block at all keeps
|
|
246
|
+
``reasoning_content=None``, so the key stays absent from stored
|
|
247
|
+
messages and no ``<think></think>`` is invented on re-render."""
|
|
248
|
+
tokenizer, renderer = _load(model)
|
|
249
|
+
stop = renderer.get_stop_token_ids()[0]
|
|
250
|
+
|
|
251
|
+
completion_ids = tokenizer.encode("cba", add_special_tokens=False) + [stop]
|
|
252
|
+
parsed = renderer.parse_response(completion_ids)
|
|
253
|
+
assert parsed.reasoning_content is None
|
|
254
|
+
assert parsed.content == "cba"
|
|
@@ -418,6 +418,31 @@ def test_chat_template_kwarg_parity_hf(
|
|
|
418
418
|
# config subclass that drops the field.
|
|
419
419
|
assert kwarg in type(renderer.config).template_field_names()
|
|
420
420
|
|
|
421
|
+
# Documented deviation: with ``enable_thinking=False`` the Qwen family
|
|
422
|
+
# re-emits the empty ``<think>\n\n</think>\n\n`` wrapper on historical
|
|
423
|
+
# assistant turns without reasoning_content, where the Jinja template
|
|
424
|
+
# strips it. The generation prompt prefills the wrapper, so stripping
|
|
425
|
+
# it on re-render would make the sampled stream and the re-render of
|
|
426
|
+
# the same conversation disagree at the token level. ``multi_turn`` is
|
|
427
|
+
# the only shape with such a turn — except on qwen3, whose template
|
|
428
|
+
# window additionally requires ``is_last or reasoning_content``, so its
|
|
429
|
+
# non-last tool-call turn in ``tool_cycle`` is stripped too. Stability
|
|
430
|
+
# is pinned in ``test_disabled_thinking_stability.py``.
|
|
431
|
+
resolved = _resolve_renderer_name(model, renderer_name)
|
|
432
|
+
deviating_shapes = (
|
|
433
|
+
("multi_turn", "tool_cycle") if resolved == "qwen3" else ("multi_turn",)
|
|
434
|
+
)
|
|
435
|
+
if (
|
|
436
|
+
resolved in ("qwen3", "qwen3.5", "qwen3.6")
|
|
437
|
+
and kwarg == "enable_thinking"
|
|
438
|
+
and value is False
|
|
439
|
+
and shape_id in deviating_shapes
|
|
440
|
+
):
|
|
441
|
+
pytest.skip(
|
|
442
|
+
"deliberate template deviation: empty think wrapper kept on "
|
|
443
|
+
"historical turns for sampled-token stability"
|
|
444
|
+
)
|
|
445
|
+
|
|
421
446
|
try:
|
|
422
447
|
expected = _expected_hf(
|
|
423
448
|
tokenizer, messages, kwarg=kwarg, value=value, **render_kwargs
|
|
@@ -9,7 +9,7 @@ resolution-markers = [
|
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
[options]
|
|
12
|
-
exclude-newer = "
|
|
12
|
+
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
|
|
13
13
|
exclude-newer-span = "P7D"
|
|
14
14
|
|
|
15
15
|
[options.exclude-newer-package]
|
|
@@ -939,75 +939,54 @@ wheels = [
|
|
|
939
939
|
|
|
940
940
|
[[package]]
|
|
941
941
|
name = "pillow"
|
|
942
|
-
version = "12.
|
|
943
|
-
source = { registry = "https://pypi.org/simple" }
|
|
944
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
945
|
-
wheels = [
|
|
946
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
947
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
948
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
949
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
950
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
951
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
952
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
953
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
954
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
955
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
956
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
957
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
958
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
959
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
960
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
961
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
962
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
963
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
964
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
965
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
966
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
967
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
968
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
969
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
970
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
971
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
972
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
973
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
974
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
975
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
976
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
977
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
978
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
979
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
980
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
981
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
982
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
983
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
984
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
985
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
986
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
987
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
988
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
989
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
990
|
-
{ url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" },
|
|
991
|
-
{ url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" },
|
|
992
|
-
{ url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" },
|
|
993
|
-
{ url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" },
|
|
994
|
-
{ url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" },
|
|
995
|
-
{ url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" },
|
|
996
|
-
{ url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" },
|
|
997
|
-
{ url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" },
|
|
998
|
-
{ url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" },
|
|
999
|
-
{ url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" },
|
|
1000
|
-
{ url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" },
|
|
1001
|
-
{ url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" },
|
|
1002
|
-
{ url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" },
|
|
1003
|
-
{ url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
|
|
1004
|
-
{ url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" },
|
|
1005
|
-
{ url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" },
|
|
1006
|
-
{ url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" },
|
|
1007
|
-
{ url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" },
|
|
1008
|
-
{ url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" },
|
|
1009
|
-
{ url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" },
|
|
1010
|
-
{ url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" },
|
|
942
|
+
version = "12.3.0"
|
|
943
|
+
source = { registry = "https://pypi.org/simple" }
|
|
944
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
|
|
945
|
+
wheels = [
|
|
946
|
+
{ url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" },
|
|
947
|
+
{ url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" },
|
|
948
|
+
{ url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" },
|
|
949
|
+
{ url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" },
|
|
950
|
+
{ url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" },
|
|
951
|
+
{ url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" },
|
|
952
|
+
{ url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" },
|
|
953
|
+
{ url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" },
|
|
954
|
+
{ url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" },
|
|
955
|
+
{ url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" },
|
|
956
|
+
{ url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" },
|
|
957
|
+
{ url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" },
|
|
958
|
+
{ url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" },
|
|
959
|
+
{ url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" },
|
|
960
|
+
{ url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" },
|
|
961
|
+
{ url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" },
|
|
962
|
+
{ url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" },
|
|
963
|
+
{ url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" },
|
|
964
|
+
{ url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" },
|
|
965
|
+
{ url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" },
|
|
966
|
+
{ url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" },
|
|
967
|
+
{ url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" },
|
|
968
|
+
{ url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" },
|
|
969
|
+
{ url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" },
|
|
970
|
+
{ url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" },
|
|
971
|
+
{ url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" },
|
|
972
|
+
{ url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" },
|
|
973
|
+
{ url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" },
|
|
974
|
+
{ url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" },
|
|
975
|
+
{ url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" },
|
|
976
|
+
{ url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" },
|
|
977
|
+
{ url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" },
|
|
978
|
+
{ url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" },
|
|
979
|
+
{ url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" },
|
|
980
|
+
{ url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" },
|
|
981
|
+
{ url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" },
|
|
982
|
+
{ url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" },
|
|
983
|
+
{ url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" },
|
|
984
|
+
{ url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" },
|
|
985
|
+
{ url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" },
|
|
986
|
+
{ url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" },
|
|
987
|
+
{ url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" },
|
|
988
|
+
{ url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" },
|
|
989
|
+
{ url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" },
|
|
1011
990
|
]
|
|
1012
991
|
|
|
1013
992
|
[[package]]
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|