Merge pull request #521 from ggozad/feat/monty-0.0.19
Update to monty 0.0.19
This commit is contained in:
commit
de102b7e34
11 changed files with 646 additions and 98 deletions
|
|
@ -1,9 +1,15 @@
|
|||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Analysis sandbox supports `open()` and `with` blocks for reading document files, including `.read()`, `.readline()`, and `.readlines()`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Require `pydantic-ai-slim>=2.18,<3`.
|
||||
- `pydantic-monty` bumped to `>=0.0.19`; sandbox code now runs in a subprocess worker pool. A crashed or timed-out worker fails one call and the next call gets a replacement session.
|
||||
- The analysis sandbox gives Monty a duration budget of `analysis.code_timeout * analysis.max_executions` for the session, was `analysis.code_timeout`.
|
||||
- `enable_thinking` maps onto Pydantic AI's unified `thinking` setting for the `anthropic`, `gemini`, `groq` and `bedrock` providers. The Anthropic thinking budget is now Pydantic AI's default of 10000 tokens, was 4096, and `max_tokens` must exceed it on budget-based Claude models; `enable_thinking: false` disables Gemini thinking rather than only hiding thoughts; Groq maps reasoning effort rather than `groq_reasoning_format`; Bedrock Qwen with `enable_thinking: false` no longer sends `reasoning_config`, while Bedrock-served Claude keeps an explicit `thinking: disabled`. The `openai` and `ollama` providers still map to `openai_reasoning_effort`.
|
||||
- `provider: bedrock` with a proprietary OpenAI model such as `openai.o3-mini-v1:0` raises `UserError`: Bedrock Converse serves only the `gpt-oss` family. Use `provider: bedrock-mantle`.
|
||||
- Tool failures raise `pydantic_ai.ToolFailed` instead of returning failure text: search and code-execution limits, sandbox execution errors, and `get_document`/`summarize_document` misses.
|
||||
|
|
@ -15,6 +21,8 @@
|
|||
- `evaluations run` opens the database read-only outside the population phase, so an embedder identity differing from the stored one warns instead of aborting the run.
|
||||
- `docs/installation.md` documents the `jina`, `s3` and `ingester` extras, names the extras the full package actually pulls, drops the removed MixedBread AI reranker, and no longer lists Anthropic as a built-in provider.
|
||||
- `docs/tuning.md` no longer points at the removed `claim_timeout_s` setting.
|
||||
- `analysis.code_timeout` is enforced before each document read, bounding a call that reads in a loop.
|
||||
- `metadata.json` in the document VFS rejects writes, matching `content.txt`, `items.jsonl` and `toc.json`.
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
|||
|
|
@ -50,13 +50,13 @@ analysis:
|
|||
provider: anthropic
|
||||
name: claude-sonnet-4-20250514
|
||||
temperature: 0.0 # Default: 0.0 (deterministic for code generation)
|
||||
code_timeout: 60.0 # Max seconds for code execution
|
||||
code_timeout: 60.0 # Max seconds a call may spend reading documents
|
||||
max_output_chars: 50000 # Truncate output after this many chars
|
||||
max_executions: 15 # Max execute_code calls per question
|
||||
```
|
||||
|
||||
- **model**: LLM configuration (see [Providers](providers.md#model-settings)). When unset, falls back to `qa.model`.
|
||||
- **code_timeout**: Maximum seconds for each code execution (default: 60)
|
||||
- **code_timeout**: Seconds a single `execute_code` call may spend reading documents (default: 60). The sandbox refuses further reads past this point. Code that computes without reading is killed by the worker watchdog at the same limit. `code_timeout * max_executions` is the cumulative ceiling across all calls in one question.
|
||||
- **max_output_chars**: Truncate code output after this many characters (default: 50000)
|
||||
- **max_executions**: Maximum `execute_code` calls per question before the capability is told to answer from what it has (default: 15)
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class AnalysisCapability(RAGCapabilityBase[AnalysisState]):
|
|||
|
||||
async def _close(self) -> None:
|
||||
if self.sandbox is not None:
|
||||
self.sandbox.close()
|
||||
await self.sandbox.close()
|
||||
self.sandbox = None
|
||||
await super()._close()
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Inside the code, these functions are available (use `await`):
|
|||
- `await list_documents()` → list of dicts with keys: id, title, uri, created_at
|
||||
|
||||
Available modules: `json`, `re`, `math`, `pathlib`
|
||||
Not supported: class definitions, generators/yield, match statements, decorators, `with` statements
|
||||
Not supported: class inheritance and metaclasses, generators/yield, match statements, decorators, `collections`, iterating a file object (`for line in f`)
|
||||
|
||||
### analysis_search
|
||||
Search the knowledge base directly (outside code execution). Each result has a `Type:` (paragraph, table, code, list_item, picture). When the Type is `picture`, the corresponding figure may also be attached to the tool response as an image alongside the text — use it directly to answer questions about figures, diagrams, charts, screenshots.
|
||||
|
|
@ -48,7 +48,7 @@ All documents are mounted as a virtual filesystem at `/documents/`:
|
|||
`{document_id}` is an internal identifier, not the user-facing `uri` (filename, URL, etc.). When you only know a document by its URI or title, use `await list_documents()` to enumerate ids and match against `uri` / `title` — that's a single call to the host. Iterating `/documents/` and reading every `metadata.json` works too but is much slower on portal-scale corpora.
|
||||
|
||||
### Reading files
|
||||
Always use `Path.read_text()` — do NOT use `open()` or `with` statements (they are not supported).
|
||||
Read with `Path.read_text()` or `open()` (including `with` blocks); file objects support `.read()`, `.readline()`, and `.readlines()`. A file object cannot be iterated, so read line-wise with `.readlines()` or `.read().split("\n")` instead of `for line in f`. Files are read-only; writing raises `PermissionError`.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
|
@ -63,7 +63,7 @@ for doc_dir in Path('/documents').iterdir():
|
|||
content = Path(f'/documents/{doc_id}/content.txt').read_text()
|
||||
|
||||
# Read and parse items
|
||||
for line in Path(f'/documents/{doc_id}/items.jsonl').read_text().strip().split(chr(10)):
|
||||
for line in Path(f'/documents/{doc_id}/items.jsonl').read_text().strip().split("\n"):
|
||||
item = json.loads(line)
|
||||
if item['label'] == 'table':
|
||||
print(item['text'][:200])
|
||||
|
|
@ -112,6 +112,6 @@ You MUST call `analysis_cite` with at least one chunk ID before producing your f
|
|||
- Use `print()` to output results — the output is your only feedback
|
||||
- When you write code, execute it — don't describe what code would do. But not every question needs code; simple lookups are best answered by `analysis_search → analysis_cite`.
|
||||
- Use `await` for all async functions inside `analysis_execute_code` (`search`, `list_documents`)
|
||||
- Use `Path.read_text()` to read files — do NOT use `open()`, `with` statements, or `collections` module
|
||||
- Read files with `Path.read_text()` or `open()`/`with`. For lines use `.readlines()` or `.read().split("\n")`, never `for line in f`. The `collections` module is unavailable.
|
||||
- Do NOT include chunk IDs or UUIDs in your answer text — your answer should read naturally. Use the `analysis_cite` tool separately to register citations. `cite{...}` markdown-style inline references do nothing; only an actual `analysis_cite` tool call registers a citation.
|
||||
- **Before you write your final answer, invoke the `analysis_cite` tool with the supporting chunk_ids.** This is the last tool call before answering whenever your answer draws on retrieved evidence.
|
||||
|
|
|
|||
|
|
@ -2,13 +2,19 @@ import asyncio
|
|||
import json
|
||||
import os
|
||||
from collections.abc import AsyncIterator, Callable, Coroutine
|
||||
from contextlib import asynccontextmanager
|
||||
from contextlib import asynccontextmanager, suppress
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
import pydantic_monty
|
||||
from pydantic_monty import CallbackFile, MemoryFile, MontyRepl, OSAccess
|
||||
from pydantic_monty import (
|
||||
AsyncMonty,
|
||||
AsyncMontySession,
|
||||
CallbackFile,
|
||||
OSAccess,
|
||||
ResourceLimits,
|
||||
)
|
||||
|
||||
from haiku.rag.config.models import AppConfig
|
||||
from haiku.rag.sandbox.dependencies import AnalysisContext
|
||||
|
|
@ -109,24 +115,26 @@ class Sandbox:
|
|||
"""Execute code in a sandboxed Python interpreter.
|
||||
|
||||
Uses pydantic-monty, a minimal secure Python interpreter written in Rust.
|
||||
External functions (search, list_documents) are called by Monty code
|
||||
The interpreter runs in a subprocess worker checked out of an ``AsyncMonty``
|
||||
pool. External functions (search, list_documents) are called by Monty code
|
||||
using ``await`` and resolved asynchronously on the host. Documents are
|
||||
exposed via a virtual filesystem at ``/documents/{id}/``.
|
||||
|
||||
The interpreter uses a REPL session — variables persist across
|
||||
``execute()`` calls within the same Sandbox instance.
|
||||
The session persists across ``execute()`` calls within the same Sandbox
|
||||
instance — variables carry over. Call ``close()`` to return the worker to
|
||||
the pool and shut the pool down.
|
||||
|
||||
sandbox = Sandbox(db_path, config, context)
|
||||
result = await sandbox.execute("x = await search('query')")
|
||||
result = await sandbox.execute("print(x[0]['content'])") # x persists
|
||||
await sandbox.close()
|
||||
|
||||
All database access runs on the event loop that drives ``execute()``. Monty's
|
||||
file callbacks are synchronous and run on the interpreter's worker thread, so
|
||||
they bridge back to that loop via ``run_coroutine_threadsafe``; the loop is
|
||||
free during ``feed_run_async`` (the VM runs on the worker thread), so the
|
||||
bridge does not deadlock. When a ``rag`` connection is supplied it is used
|
||||
for every read, so an analysis run drives a single connection on a single
|
||||
loop; otherwise each read opens an ephemeral read-only connection.
|
||||
file callbacks are synchronous and run off that loop while ``feed_run`` is
|
||||
awaited, so they bridge back to it via ``run_coroutine_threadsafe`` without
|
||||
deadlocking. When a ``rag`` connection is supplied it is used for every read,
|
||||
so an analysis run drives a single connection on a single loop; otherwise
|
||||
each read opens an ephemeral read-only connection.
|
||||
"""
|
||||
|
||||
_db_path: Path
|
||||
|
|
@ -139,9 +147,11 @@ class Sandbox:
|
|||
_doc_chunk_index: dict[str, dict[str, list[str]]]
|
||||
_items_jsonl_cache: dict[str, str]
|
||||
_toc_json_cache: dict[str, str]
|
||||
_repl: MontyRepl | None
|
||||
_pool: AsyncMonty | None
|
||||
_session: AsyncMontySession | None
|
||||
_vfs: OSAccess | None
|
||||
_loop: asyncio.AbstractEventLoop | None
|
||||
_deadline: float | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -161,9 +171,11 @@ class Sandbox:
|
|||
self._doc_chunk_index = {}
|
||||
self._items_jsonl_cache = {}
|
||||
self._toc_json_cache = {}
|
||||
self._repl = None
|
||||
self._pool = None
|
||||
self._session = None
|
||||
self._vfs = None
|
||||
self._loop = None
|
||||
self._deadline = None
|
||||
|
||||
@asynccontextmanager
|
||||
async def _connection(self) -> "AsyncIterator[HaikuRAG]":
|
||||
|
|
@ -185,17 +197,49 @@ class Sandbox:
|
|||
def _run_on_loop(self, coro: Coroutine[Any, Any, Any]) -> Any:
|
||||
"""Run a coroutine on the execute() loop from a synchronous callback.
|
||||
|
||||
Called from Monty's worker thread while ``feed_run_async`` leaves the
|
||||
loop free, so scheduling onto it and blocking for the result is safe.
|
||||
Called off the loop while ``feed_run`` is awaited, so scheduling onto it
|
||||
and blocking for the result is safe.
|
||||
|
||||
Blocking here suspends the worker, and Monty checks its duration budget
|
||||
between interpreter steps, so it cannot check while a read is in flight.
|
||||
Enforce the budget before starting another read, or code that reads in a
|
||||
loop overruns it by however long the outstanding reads take. Raising from
|
||||
inside the callback answers the worker's suspension, which keeps the
|
||||
session usable — cancelling ``feed_run`` from outside does not, and wedges
|
||||
the protocol.
|
||||
"""
|
||||
assert self._loop is not None, (
|
||||
"VFS reads happen during execute(); the loop must be captured first."
|
||||
)
|
||||
if self._deadline is not None and self._loop.time() > self._deadline:
|
||||
coro.close()
|
||||
raise TimeoutError(
|
||||
"time limit exceeded: no further document reads after "
|
||||
f"{self._config.analysis.code_timeout}s"
|
||||
)
|
||||
return asyncio.run_coroutine_threadsafe(coro, self._loop).result()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Retained for API compatibility; the sandbox owns no resources."""
|
||||
return
|
||||
async def _discard_session(self) -> None:
|
||||
"""Drop a session whose worker is gone.
|
||||
|
||||
The session object is unusable once its worker dies: it answers every
|
||||
later call with ``RuntimeError: this checkout has already been
|
||||
finished``. Clearing it makes ``_ensure_initialized`` check out a
|
||||
replacement, at the cost of the variables the dead worker held.
|
||||
"""
|
||||
session, self._session = self._session, None
|
||||
if session is not None:
|
||||
with suppress(Exception):
|
||||
await session.__aexit__(None, None, None)
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Return the worker to the pool and shut the pool down. Idempotent."""
|
||||
if self._session is not None:
|
||||
await self._session.__aexit__(None, None, None)
|
||||
self._session = None
|
||||
if self._pool is not None:
|
||||
await self._pool.__aexit__(None, None, None)
|
||||
self._pool = None
|
||||
|
||||
def _build_external_functions(self) -> dict[str, Any]:
|
||||
"""Build async external functions for the Monty interpreter."""
|
||||
|
|
@ -255,12 +299,12 @@ class Sandbox:
|
|||
"""Build the virtual filesystem with document data.
|
||||
|
||||
Mounts per-document directories with:
|
||||
- metadata.json: MemoryFile (eager, small)
|
||||
- metadata.json: CallbackFile (eager, small)
|
||||
- content.txt: CallbackFile (lazy, can be large)
|
||||
- items.jsonl: CallbackFile (lazy, bulk-cached)
|
||||
- toc.json: CallbackFile (lazy, bulk-cached)
|
||||
"""
|
||||
files: list[MemoryFile | CallbackFile] = []
|
||||
files: list[CallbackFile] = []
|
||||
|
||||
def _deny_write(_path: "PurePosixPath", _content: str | bytes) -> None:
|
||||
raise PermissionError(f"Document files are read-only: {_path}")
|
||||
|
|
@ -370,7 +414,16 @@ class Sandbox:
|
|||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
files.append(MemoryFile(f"{doc_dir}/metadata.json", metadata))
|
||||
|
||||
# MemoryFile has no write hook, so metadata.json goes through the
|
||||
# same read and deny pair as the rest. Its content is already built.
|
||||
files.append(
|
||||
CallbackFile(
|
||||
f"{doc_dir}/metadata.json",
|
||||
read=lambda _path, text=metadata: text,
|
||||
write=_deny_write,
|
||||
)
|
||||
)
|
||||
|
||||
def _make_content_reader(
|
||||
did: str,
|
||||
|
|
@ -413,52 +466,79 @@ class Sandbox:
|
|||
|
||||
return OSAccess(files)
|
||||
|
||||
async def _ensure_initialized(self) -> tuple[MontyRepl, OSAccess]:
|
||||
"""Initialize the REPL session and VFS on first use."""
|
||||
if self._repl is None:
|
||||
def _session_limits(self) -> ResourceLimits:
|
||||
"""Resource limits for the worker session.
|
||||
|
||||
Monty spends ``max_duration_secs`` across the session's whole life, and
|
||||
the session is reused so variables persist between calls. Budget it for
|
||||
the run rather than for one call, or the first slow call starves every
|
||||
later one. ``code_timeout`` is enforced per call elsewhere: the read
|
||||
deadline in ``_run_on_loop`` bounds a call that reads, and the pool's
|
||||
``request_timeout`` bounds one that computes.
|
||||
"""
|
||||
analysis = self._config.analysis
|
||||
return {"max_duration_secs": analysis.code_timeout * analysis.max_executions}
|
||||
|
||||
async def _ensure_initialized(self) -> tuple[AsyncMontySession, OSAccess]:
|
||||
"""Check out a worker session and build the VFS on first use."""
|
||||
if self._vfs is None:
|
||||
self._vfs = await self._build_vfs()
|
||||
self._repl = MontyRepl(
|
||||
limits={
|
||||
"max_duration_secs": self._config.analysis.code_timeout,
|
||||
},
|
||||
)
|
||||
assert self._repl is not None and self._vfs is not None
|
||||
return self._repl, self._vfs
|
||||
if self._pool is None:
|
||||
# The watchdog counts only time the worker spends running code, so a
|
||||
# read that blocks the worker never trips it. That leaves the two
|
||||
# limits disjoint: this one bounds a call that computes, and the read
|
||||
# deadline bounds a call that reads.
|
||||
pool = AsyncMonty(request_timeout=self._config.analysis.code_timeout)
|
||||
await pool.__aenter__()
|
||||
self._pool = pool
|
||||
if self._session is None:
|
||||
session = self._pool.checkout(limits=self._session_limits())
|
||||
await session.__aenter__()
|
||||
self._session = session
|
||||
assert self._session is not None and self._vfs is not None
|
||||
return self._session, self._vfs
|
||||
|
||||
async def execute(self, code: str) -> SandboxResult:
|
||||
"""Execute Python code in the Monty REPL.
|
||||
"""Execute Python code in the Monty worker session.
|
||||
|
||||
Variables persist across calls within the same Sandbox instance.
|
||||
"""
|
||||
# Monty's synchronous file callbacks bridge DB reads back to this loop.
|
||||
self._loop = asyncio.get_running_loop()
|
||||
repl, vfs = await self._ensure_initialized()
|
||||
self._deadline = self._loop.time() + self._config.analysis.code_timeout
|
||||
session, vfs = await self._ensure_initialized()
|
||||
external_fns = self._build_external_functions()
|
||||
|
||||
stdout_lines: list[str] = []
|
||||
|
||||
def print_callback( # pragma: no cover - runs on Monty's Rust thread
|
||||
_stream: Literal["stdout"], text: str
|
||||
def print_callback( # pragma: no cover - runs on Monty's worker thread
|
||||
_stream: Literal["stdout", "stderr"], text: str
|
||||
) -> None:
|
||||
stdout_lines.append(text)
|
||||
|
||||
max_chars = self._config.analysis.max_output_chars
|
||||
|
||||
try:
|
||||
output = await repl.feed_run_async(
|
||||
output = await session.feed_run(
|
||||
code,
|
||||
external_functions=external_fns,
|
||||
external_lookup=external_fns,
|
||||
print_callback=print_callback,
|
||||
os=vfs,
|
||||
)
|
||||
except (
|
||||
pydantic_monty.MontySyntaxError,
|
||||
pydantic_monty.MontyRuntimeError,
|
||||
) as e:
|
||||
except (pydantic_monty.MontyError, RuntimeError) as e:
|
||||
stdout = "".join(stdout_lines)
|
||||
if len(stdout) > max_chars:
|
||||
stdout = stdout[:max_chars] + "\n... (output truncated)"
|
||||
return SandboxResult(stdout=stdout, stderr=str(e), success=False)
|
||||
stderr = str(e)
|
||||
# A crash kills the worker, and a protocol error leaves it out of
|
||||
# step. Both poison the session. Bad user code does not.
|
||||
if isinstance(e, pydantic_monty.MontyCrashedError | RuntimeError):
|
||||
await self._discard_session()
|
||||
stderr = (
|
||||
f"{stderr}\n\nThe interpreter restarted. Variables from "
|
||||
"earlier calls are gone."
|
||||
)
|
||||
return SandboxResult(stdout=stdout, stderr=stderr, success=False)
|
||||
|
||||
stdout = "".join(stdout_lines)
|
||||
if output is not None:
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ dependencies = [
|
|||
"pathspec>=1.0.4",
|
||||
"pydantic>=2.12.5",
|
||||
"pydantic-ai-slim[openai,logfire,ag-ui]>=2.18.0,<3.0.0",
|
||||
"pydantic-monty>=0.0.17",
|
||||
"pydantic-monty>=0.0.19",
|
||||
"pypdfium2>=5.0",
|
||||
"python-dotenv>=1.2.2",
|
||||
"pyyaml>=6.0.3",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -7,6 +7,7 @@ import pytest
|
|||
from haiku.rag.client import HaikuRAG
|
||||
from haiku.rag.config.models import AppConfig
|
||||
from haiku.rag.sandbox import AnalysisContext, Sandbox, SandboxResult
|
||||
from haiku.rag.store.models.chunk import Chunk
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
|
@ -383,6 +384,175 @@ class TestSandboxVFS:
|
|||
assert result.success
|
||||
assert result.stdout.count("True") == 6
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.vcr()
|
||||
async def test_open_read(self, temp_db_path):
|
||||
"""open() and a with-block read document files through the VFS."""
|
||||
config = AppConfig()
|
||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||
doc = await client.create_document(
|
||||
content="Content about foxes and dogs.",
|
||||
uri="test://doc",
|
||||
title="Fox Document",
|
||||
)
|
||||
|
||||
context = AnalysisContext()
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=context)
|
||||
result = await sb.execute(
|
||||
f"with open('/documents/{doc.id}/content.txt') as f:\n"
|
||||
" data = f.read()\n"
|
||||
"print('foxes' in data.lower())"
|
||||
)
|
||||
assert result.success
|
||||
assert "True" in result.stdout
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.vcr()
|
||||
async def test_open_readlines(self, temp_db_path):
|
||||
"""readlines() splits a newline-delimited VFS file into lines."""
|
||||
config = AppConfig()
|
||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||
doc = await client.create_document(
|
||||
content="The quick brown fox jumps over the lazy dog.",
|
||||
uri="test://animals",
|
||||
title="Animals",
|
||||
)
|
||||
|
||||
context = AnalysisContext()
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=context)
|
||||
result = await sb.execute(
|
||||
f"lines = open('/documents/{doc.id}/items.jsonl').readlines()\n"
|
||||
"print(len(lines) > 0)\n"
|
||||
"import json\n"
|
||||
"print('self_ref' in json.loads(lines[0]))"
|
||||
)
|
||||
assert result.success
|
||||
assert result.stdout.count("True") == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"filename", ["content.txt", "items.jsonl", "toc.json", "metadata.json"]
|
||||
)
|
||||
async def test_write_denied_for_every_document_file(self, temp_db_path, filename):
|
||||
"""Every file in the document VFS is read-only, metadata.json included."""
|
||||
from docling_core.types.doc.document import DoclingDocument
|
||||
from docling_core.types.doc.labels import DocItemLabel
|
||||
|
||||
config = AppConfig()
|
||||
docling = DoclingDocument(name="d")
|
||||
docling.add_text(label=DocItemLabel.TEXT, text="Foxes and dogs.")
|
||||
|
||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||
doc = await client.import_document(
|
||||
docling,
|
||||
[
|
||||
Chunk(
|
||||
content="Foxes and dogs.",
|
||||
embedding=[0.1] * config.embeddings.model.vector_dim,
|
||||
order=0,
|
||||
)
|
||||
],
|
||||
uri="test://readonly",
|
||||
)
|
||||
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
|
||||
try:
|
||||
result = await sb.execute(
|
||||
"from pathlib import Path\n"
|
||||
"try:\n"
|
||||
f" Path('/documents/{doc.id}/{filename}').write_text('nope')\n"
|
||||
" print('WROTE')\n"
|
||||
"except PermissionError:\n"
|
||||
" print('DENIED')"
|
||||
)
|
||||
assert result.success, result.stderr
|
||||
assert "DENIED" in result.stdout
|
||||
assert "WROTE" not in result.stdout
|
||||
finally:
|
||||
await sb.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_open_for_writing_is_denied(self, temp_db_path):
|
||||
"""`open()` in write mode is refused, not only `Path.write_text`."""
|
||||
from docling_core.types.doc.document import DoclingDocument
|
||||
from docling_core.types.doc.labels import DocItemLabel
|
||||
|
||||
config = AppConfig()
|
||||
docling = DoclingDocument(name="d")
|
||||
docling.add_text(label=DocItemLabel.TEXT, text="Foxes and dogs.")
|
||||
|
||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||
doc = await client.import_document(
|
||||
docling,
|
||||
[
|
||||
Chunk(
|
||||
content="Foxes and dogs.",
|
||||
embedding=[0.1] * config.embeddings.model.vector_dim,
|
||||
order=0,
|
||||
)
|
||||
],
|
||||
uri="test://openwrite",
|
||||
)
|
||||
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
|
||||
try:
|
||||
result = await sb.execute(
|
||||
"try:\n"
|
||||
f" with open('/documents/{doc.id}/content.txt', 'w') as f:\n"
|
||||
" f.write('nope')\n"
|
||||
" print('WROTE')\n"
|
||||
"except PermissionError:\n"
|
||||
" print('DENIED')"
|
||||
)
|
||||
assert result.success, result.stderr
|
||||
assert "DENIED" in result.stdout
|
||||
assert "WROTE" not in result.stdout
|
||||
finally:
|
||||
await sb.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_open_file_objects_are_not_iterable(self, temp_db_path):
|
||||
"""Pins the limitation the instructions warn about: pydantic/monty#490.
|
||||
|
||||
A failure here means Monty gained iteration support and the
|
||||
`for line in f` prohibition in the analysis instructions is now wrong.
|
||||
"""
|
||||
from docling_core.types.doc.document import DoclingDocument
|
||||
from docling_core.types.doc.labels import DocItemLabel
|
||||
|
||||
config = AppConfig()
|
||||
docling = DoclingDocument(name="d")
|
||||
docling.add_text(label=DocItemLabel.TEXT, text="one\ntwo")
|
||||
|
||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||
doc = await client.import_document(
|
||||
docling,
|
||||
[
|
||||
Chunk(
|
||||
content="one\ntwo",
|
||||
embedding=[0.1] * config.embeddings.model.vector_dim,
|
||||
order=0,
|
||||
)
|
||||
],
|
||||
uri="test://lines",
|
||||
)
|
||||
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
|
||||
try:
|
||||
result = await sb.execute(
|
||||
f"for line in open('/documents/{doc.id}/content.txt'):\n print(line)"
|
||||
)
|
||||
assert result.success is False
|
||||
assert "not iterable" in result.stderr
|
||||
|
||||
# The documented alternatives do work.
|
||||
result = await sb.execute(
|
||||
f"print(len(open('/documents/{doc.id}/content.txt').readlines()))"
|
||||
)
|
||||
assert result.success, result.stderr
|
||||
finally:
|
||||
await sb.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.vcr()
|
||||
async def test_context_filter_limits_vfs(self, temp_db_path):
|
||||
|
|
@ -453,7 +623,7 @@ class TestSandboxHeldConnection:
|
|||
assert int(lines[1]) > 0
|
||||
assert lines[2] == "True"
|
||||
finally:
|
||||
sb.close()
|
||||
await sb.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.vcr()
|
||||
|
|
@ -557,7 +727,7 @@ class TestSandboxHeldConnection:
|
|||
assert first.stdout == second.stdout
|
||||
assert not any(t.name == "sandbox-vfs" for t in threading.enumerate())
|
||||
finally:
|
||||
sb.close()
|
||||
await sb.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.vcr()
|
||||
|
|
@ -584,21 +754,21 @@ class TestSandboxHeldConnection:
|
|||
assert second.success, second.stderr
|
||||
assert int(second.stdout.strip()) > 0
|
||||
finally:
|
||||
sb.close()
|
||||
await sb.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_close_is_safe_without_vfs_read(self, temp_db_path):
|
||||
"""close() is a no-op (and safe to call twice) when no VFS read happened."""
|
||||
"""close() is safe and idempotent before any code has run."""
|
||||
async with HaikuRAG(temp_db_path, create=True):
|
||||
config = AppConfig()
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
|
||||
sb.close()
|
||||
sb.close()
|
||||
await sb.close()
|
||||
await sb.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.vcr()
|
||||
async def test_close_is_idempotent_after_vfs_read(self, temp_db_path):
|
||||
"""close() is a safe no-op after a VFS read; no background thread lingers."""
|
||||
"""close() tears down the worker and is idempotent; no thread lingers."""
|
||||
config = AppConfig()
|
||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||
doc = await client.create_document(
|
||||
|
|
@ -616,5 +786,149 @@ class TestSandboxHeldConnection:
|
|||
)
|
||||
assert result.success
|
||||
assert not any(t.name == "sandbox-vfs" for t in threading.enumerate())
|
||||
sb.close()
|
||||
sb.close()
|
||||
await sb.close()
|
||||
await sb.close()
|
||||
|
||||
|
||||
class TestSandboxReadDeadline:
|
||||
"""The VFS bridge suspends the worker for the length of a read, so Monty
|
||||
cannot check its duration budget while one is in flight. The sandbox
|
||||
enforces the budget itself, before each read."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_after_deadline_raises_without_scheduling(self, sandbox):
|
||||
"""A read attempted past the deadline fails instead of querying."""
|
||||
scheduled = False
|
||||
|
||||
async def _never_runs():
|
||||
nonlocal scheduled
|
||||
scheduled = True
|
||||
|
||||
sandbox._loop = asyncio.get_running_loop()
|
||||
sandbox._deadline = sandbox._loop.time() - 1.0
|
||||
|
||||
coro = _never_runs()
|
||||
with pytest.raises(TimeoutError, match="time limit exceeded"):
|
||||
sandbox._run_on_loop(coro)
|
||||
|
||||
coro.close()
|
||||
assert scheduled is False
|
||||
|
||||
def test_session_budget_covers_every_permitted_execution(self, temp_db_path):
|
||||
"""Monty spends its duration budget across the session's whole life, so a
|
||||
per-call value would let the first call starve the rest."""
|
||||
config = AppConfig()
|
||||
config.analysis.code_timeout = 5.0
|
||||
config.analysis.max_executions = 3
|
||||
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
|
||||
|
||||
assert sb._session_limits() == {"max_duration_secs": 15.0}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refused_read_fails_the_execution(self, temp_db_path, monkeypatch):
|
||||
"""The refusal surfaces as a failed result, not a raised exception."""
|
||||
from docling_core.types.doc.document import DoclingDocument
|
||||
from docling_core.types.doc.labels import DocItemLabel
|
||||
|
||||
config = AppConfig()
|
||||
docling = DoclingDocument(name="d")
|
||||
docling.add_text(label=DocItemLabel.TEXT, text="Foxes and dogs.")
|
||||
|
||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||
doc = await client.import_document(
|
||||
docling,
|
||||
[
|
||||
Chunk(
|
||||
content="Foxes and dogs.",
|
||||
embedding=[0.1] * config.embeddings.model.vector_dim,
|
||||
order=0,
|
||||
)
|
||||
],
|
||||
uri="test://deadline",
|
||||
)
|
||||
|
||||
def _past_deadline(*_args, **_kwargs):
|
||||
raise TimeoutError(
|
||||
"time limit exceeded: no further document reads after 60.0s"
|
||||
)
|
||||
|
||||
monkeypatch.setattr(Sandbox, "_run_on_loop", _past_deadline)
|
||||
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
|
||||
try:
|
||||
result = await sb.execute(
|
||||
"from pathlib import Path\n"
|
||||
f"print(Path('/documents/{doc.id}/content.txt').read_text())"
|
||||
)
|
||||
assert result.success is False
|
||||
assert "no further document reads" in result.stderr
|
||||
finally:
|
||||
await sb.close()
|
||||
|
||||
|
||||
class TestSandboxWorkerCrash:
|
||||
"""A dead worker must not poison every later call in the run."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_crashed_worker_is_replaced(self, temp_db_path):
|
||||
"""The crash fails one call. The next call gets a fresh session."""
|
||||
import os
|
||||
import signal
|
||||
|
||||
config = AppConfig()
|
||||
async with HaikuRAG(temp_db_path, create=True):
|
||||
pass
|
||||
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
|
||||
try:
|
||||
first = await sb.execute("x = 1\nprint(x)")
|
||||
assert first.success, first.stderr
|
||||
assert sb._session is not None
|
||||
pid = sb._session.worker_pid
|
||||
assert pid is not None
|
||||
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
|
||||
crashed = await sb.execute("print(2)")
|
||||
assert crashed.success is False
|
||||
assert "restarted" in crashed.stderr
|
||||
|
||||
# Without the discard every later call fails on the dead session.
|
||||
recovered = await sb.execute("print(3)")
|
||||
assert recovered.success, recovered.stderr
|
||||
assert "3" in recovered.stdout
|
||||
|
||||
# The replacement worker starts empty, which the failure said.
|
||||
lost = await sb.execute("print(x)")
|
||||
assert lost.success is False
|
||||
finally:
|
||||
await sb.close()
|
||||
|
||||
|
||||
class TestSandboxRequestTimeout:
|
||||
"""The pool watchdog bounds a call that never reads."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runaway_compute_is_killed_and_the_next_call_recovers(
|
||||
self, temp_db_path
|
||||
):
|
||||
"""Code that never reads escapes the read deadline. The watchdog kills it."""
|
||||
config = AppConfig()
|
||||
config.analysis.code_timeout = 1.0
|
||||
async with HaikuRAG(temp_db_path, create=True):
|
||||
pass
|
||||
|
||||
sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext())
|
||||
try:
|
||||
runaway = await sb.execute(
|
||||
"x = 0\nfor i in range(500000000):\n x += i\nprint(x)"
|
||||
)
|
||||
assert runaway.success is False
|
||||
assert "restarted" in runaway.stderr
|
||||
|
||||
recovered = await sb.execute("print('alive')")
|
||||
assert recovered.success, recovered.stderr
|
||||
assert "alive" in recovered.stdout
|
||||
finally:
|
||||
await sb.close()
|
||||
|
|
|
|||
|
|
@ -83,6 +83,23 @@ def _flatten(tree: list[dict]) -> list[dict]:
|
|||
return out
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestMetadataJson:
|
||||
"""metadata.json is served by a reader callback, like the other VFS files."""
|
||||
|
||||
async def test_metadata_reader_returns_document_fields(self, temp_db_path):
|
||||
async with HaikuRAG(temp_db_path, create=True) as client:
|
||||
doc_id = await _empty_doc(client, uri="test://meta", title="Meta Doc")
|
||||
|
||||
sandbox = Sandbox(temp_db_path, AppConfig(), AnalysisContext())
|
||||
raw = await _read_vfs_text(sandbox, f"/documents/{doc_id}/metadata.json")
|
||||
meta = json.loads(raw)
|
||||
|
||||
assert meta["id"] == doc_id
|
||||
assert meta["title"] == "Meta Doc"
|
||||
assert meta["uri"] == "test://meta"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestTocShape:
|
||||
"""toc.json builds a section tree from heading_level + position."""
|
||||
|
|
|
|||
123
uv.lock
123
uv.lock
|
|
@ -1770,7 +1770,7 @@ requires-dist = [
|
|||
{ name = "pydantic-ai-slim", extras = ["mistral"], marker = "extra == 'mistral'" },
|
||||
{ name = "pydantic-ai-slim", extras = ["openai", "logfire", "ag-ui"], specifier = ">=2.18.0,<3.0.0" },
|
||||
{ name = "pydantic-ai-slim", extras = ["voyageai"], marker = "extra == 'voyageai'" },
|
||||
{ name = "pydantic-monty", specifier = ">=0.0.17" },
|
||||
{ name = "pydantic-monty", specifier = ">=0.0.19" },
|
||||
{ name = "pypdfium2", specifier = ">=5.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
||||
{ name = "pyyaml", specifier = ">=6.0.3" },
|
||||
|
|
@ -3981,49 +3981,94 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pydantic-monty"
|
||||
version = "0.0.17"
|
||||
version = "0.0.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic-monty-runtime" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/f8/431ba0b79d02922811392c4e3d283d6508f7052ceaa1936cc34878703ecc/pydantic_monty-0.0.17.tar.gz", hash = "sha256:9c4904a8fbc63282793f3afd2d180124494c7fc371783f365e5691c9586360af", size = 1007724, upload-time = "2026-04-22T20:13:48.915Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/f8/c04df414086488834d102ab85cf8172c114108f842fb142ac92a490213fd/pydantic_monty-0.0.19.tar.gz", hash = "sha256:f3f9e256058b4085349dd4ad347795d5203a8310e9eaad9a2bff93890ac5b86d", size = 1484032, upload-time = "2026-07-24T10:00:13.194Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/31/95827babdb35149f076c5d191b6b1e7a7c58f4bc72432f905e02e4e3231e/pydantic_monty-0.0.17-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27c2254fa7a7b05e969f79578889230d293c62e0b1ee28371ec4f3c54b14426a", size = 7342248, upload-time = "2026-04-22T20:15:18.775Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/67/ca9cfc07cd445d22def53e9db86912f9ae3e11ef772ce41c2ff41a47eac5/pydantic_monty-0.0.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:445cc471ce6f5a88ef06741b7ebc7002a2253d182f55a2f47094d4adedaaf497", size = 7311255, upload-time = "2026-04-22T20:15:27.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/96/abc9c4972d91a9673435b84e12b99d038e42d1f99648fc9e5f242e09d00e/pydantic_monty-0.0.17-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:39121038405911f59da7bf61164251f59bad3fb1b0cd28f43c42c3949eee2c8a", size = 7868109, upload-time = "2026-04-22T20:15:04.779Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/82/9e4d55529bb99d882b9277a721762537a8bc1345ab1d052bb614a88bd15b/pydantic_monty-0.0.17-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dafc8ffe57c257002f623afdb7d0e41f73de850179ebd90b42611e4f2b6f9884", size = 7139709, upload-time = "2026-04-22T20:15:25.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/97/f1af6acefb7bb38d73934d6853998bbd327de7418b811372519080d9fd84/pydantic_monty-0.0.17-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea00838ef8f37dcd8085defcbdfe89fdd05297a6533f1d3f4cad857d13cedc7b", size = 7450444, upload-time = "2026-04-22T20:13:37.974Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/91/af92ef409e1c065345cf1451bbcf19e00f70a250b8372ec65143ca9a9238/pydantic_monty-0.0.17-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683d18089acf14d0de293245b9e37c7f0ec64e6d266f6773144211931aa3ec97", size = 7967525, upload-time = "2026-04-22T20:13:42.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/1f/23ecd6e268ef24ce6b0fe4a1e76a314990d2e923ac5791e29d657418243d/pydantic_monty-0.0.17-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1829993dd50cf497cbed66ea9f6c8ff7d157d22592a05c7399f92fb8a549e3c", size = 8199124, upload-time = "2026-04-22T20:15:00.02Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/2a/36b694ea0c7e202250a81a57faf00f218738da6c5d070c752f2d81cd34ce/pydantic_monty-0.0.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a7869e3f41a54cc588096c52a8a4de25ecd81e75c867ea4164b14ea1ae1a57f", size = 7739623, upload-time = "2026-04-22T20:14:37.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/e5/090357d7bc0f0751d1afbb71330695fa26554699c88ba56ecaad91657088/pydantic_monty-0.0.17-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9c17663e2c6f07aec5bc54cd7e39a9e20f250a97a9081e1b2b932eb00d0afc5", size = 7317755, upload-time = "2026-04-22T20:14:48.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/63/67200070cf33325ecfda81d4aee3bf312250ce80bd73058103e04e0f3587/pydantic_monty-0.0.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5d2cf98afe2fb124f6ade91d9663d54277478cad417164f83df1854a41ef450c", size = 7769158, upload-time = "2026-04-22T20:14:39.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/ce/9ecfbc2f45406cfb247fafdea4f4a8412db3e559a22c4385eb15266ba2c1/pydantic_monty-0.0.17-cp312-cp312-win32.whl", hash = "sha256:b2185cc4effbbd6793eed4e0f0bcb6a3dbfbb3289ea4d47888708813f0a3dd47", size = 7227917, upload-time = "2026-04-22T20:14:15.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/80/9be3bef8273817ccc17da25c3ce4ff5d5d45e5629c17eacc90cdef073821/pydantic_monty-0.0.17-cp312-cp312-win_amd64.whl", hash = "sha256:7833daed757ec9b09b627cc3577a4a76b114c5148f779531d7cfdb1095bcf0a9", size = 8043469, upload-time = "2026-04-22T20:13:22.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/44/0e106b8b27eb93b66e4f3d279486464e05ba5ee31088848e58b5f506f879/pydantic_monty-0.0.17-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0cdac8c3c16477596bc96ee1cec4f2fbaccd089e2daa1e7b9f227cc89f97cb1", size = 7341507, upload-time = "2026-04-22T20:14:41.717Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/88/a0315fa08e62e2d1ef00c03d8202d7bef3f1f71543bebfb916fea265c0a2/pydantic_monty-0.0.17-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37290d6a1c35aba5cfb8b490bb31c0d822e8ddca8f3ca9ea068e30930d80dd1e", size = 7311916, upload-time = "2026-04-22T20:13:34.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/e5/e4da6acb408594cbfbfb8dd3c0491b9b2ee54e9183e7ebc5f584baa07af9/pydantic_monty-0.0.17-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:49f252b2fb918686d3e8f76cb30245e782d1560a7fa68dbc0f6940d83c12bd41", size = 7867465, upload-time = "2026-04-22T20:13:31.466Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/d4/64c2f8eb708a743b0944ea8f71dfd51bc655285b4be28d55577dafbb29fa/pydantic_monty-0.0.17-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2254d25c34463d67069f5f1567157bde9311654cd5371f8933b8ea9815bfa26a", size = 7139262, upload-time = "2026-04-22T20:14:10.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/67/5d766f9cd304e871a5dfe5f0a85eaa533538ef07e9b2858fdf9f37f83694/pydantic_monty-0.0.17-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0688a1fa5dc045ac7b7e996d7269b94f74f116d7b1e352c7b5bb5ad53d4fe03", size = 7450119, upload-time = "2026-04-22T20:13:44.515Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/98/fa16779021d93edb19807e87cdba56bbec6adfad21f10b41a212572ce513/pydantic_monty-0.0.17-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b35121ac555ed201405c69d531e4cb916da6984b8cd2e15c8a319117349faf6", size = 7967398, upload-time = "2026-04-22T20:13:55.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/64/287a42720bc9e975ab5b52625aa9fc6bcef8298dd821022cc45c6ee1808d/pydantic_monty-0.0.17-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c97dc44af25d4392b474902fc40f78f09fe8f44ba791364670334fe12abc077", size = 8198835, upload-time = "2026-04-22T20:15:02.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/37/03edb1fd582b79b2b462afc3fea5e1c8fea73afefc4870dc35fc3c7c492e/pydantic_monty-0.0.17-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ed2fb365ef9ca921de9a17786ecfa2efe06e65678e6ca57be51658a2a880f31", size = 7739241, upload-time = "2026-04-22T20:13:46.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/0b/b765c9ca2ae27def1caa07345aba073ae1239fc2d9cca7a375f3dc2195f7/pydantic_monty-0.0.17-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5bf9f07b38dd12747e3c95b169a5afb3e2e9107622e01e548246c84d19a69c99", size = 7316719, upload-time = "2026-04-22T20:14:13.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/3b/64fe872cd575ab5262e1ba2959554ead198c939cfaa425f7f8e9b1ad2694/pydantic_monty-0.0.17-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a5e9bafd4b5acbc0a8e12ee8403a3ce37281c3b0fa5909d3f412bee76c69003c", size = 7769150, upload-time = "2026-04-22T20:13:57.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/b4/b6a0bb41f39bac2e11e6a2fd42ca0886893fafa6344fb17c3f0a94e22e83/pydantic_monty-0.0.17-cp313-cp313-win32.whl", hash = "sha256:1c239ae3e610d3f39cd1609285209a4e2d046b465ac1bfed0d4374c615eed0fa", size = 7227705, upload-time = "2026-04-22T20:15:12.262Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/e7/d8cd62f537f7ab17714ee19ea221a0e341dab407215376ab1c41d79794c9/pydantic_monty-0.0.17-cp313-cp313-win_amd64.whl", hash = "sha256:1886c3590b02f359ae991f1e76691064f167330eda4fbf22762127ce17d0eb48", size = 8043469, upload-time = "2026-04-22T20:14:24.86Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/c0/8354baf835e1a04c4b9e11d253f82df7d625a9305e6a23a177fb895b1484/pydantic_monty-0.0.17-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:a166bd04d1996f0d144fbb5e1391cd1c0fbdacd4fa3b689dd48931388679fe98", size = 7341303, upload-time = "2026-04-22T20:14:35.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/ac/d58221b5e17915421ca00bb08b805ac121b6accb194785d5422da4a2f5fc/pydantic_monty-0.0.17-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d82f319e3fd79707a7b81bbb68596509d14ac73502d2f0daf4ab5d281efdfbdc", size = 7318912, upload-time = "2026-04-22T20:13:59.966Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/3f/8eeb8f652f6cd6e06a737aa9f00de2949e37a669b31756bc51b6182457b4/pydantic_monty-0.0.17-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f38b9875f7ff56fe69538b60b2ecbdcbe2b8b7780407ceb05fe0ee1414bf8d19", size = 7867027, upload-time = "2026-04-22T20:13:51.426Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/ba/ec6620c27c8b4cada6ce53378c52c245e238e50a20b968cafdc1b8573c4e/pydantic_monty-0.0.17-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74a778bb5a4dcdbc85b3b9002f9a72a43fb6ffd88635bfdd502e8d3053008337", size = 7137542, upload-time = "2026-04-22T20:13:35.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/78/5419785630511b54b15cfb094871bcb53ec9025ba8d91bd7ab5b22b6c98f/pydantic_monty-0.0.17-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e2ab074a65738e9c1b4be9a432e7ca1e9987a6706018dc7a5af6d4ce7cecdf3", size = 7450222, upload-time = "2026-04-22T20:13:53.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/04/cec11fa96a47034da3c21af53e73f43d2270f5ce96cd710809859fe9c0b2/pydantic_monty-0.0.17-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00fd1cd28b4200c9ccd02629868486b366af1bfe1f0584d4c9e513b7a941a868", size = 7967405, upload-time = "2026-04-22T20:14:03.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/e3/f2be0fb975100b6936ca36a8410098f10fab3b26729c0b0d1de2fac59ff3/pydantic_monty-0.0.17-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ea6684555bfd00cbe9d2df3e73caada3d82200c168c63cc475197b55b88401", size = 8199028, upload-time = "2026-04-22T20:13:26.802Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/43/358bdaa9c50d21fe4a25a71d43ce9af2d6796616fa47aca84f807433564e/pydantic_monty-0.0.17-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f804a03a3bbd0cf0ade1d4ce11b50ca6858e9c4440b27c746faf1d3c0a272954", size = 7749903, upload-time = "2026-04-22T20:15:09.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/da/8bcd0a78abf13edceca36aaf5c180fad963e4c2e042fd7247b9e96048306/pydantic_monty-0.0.17-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:b5476e6c08b86b0bea554b97ce9b142aac1177447f2d3c5751864b27991fd1b1", size = 7312704, upload-time = "2026-04-22T20:14:33.668Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/49/5de8bb7f8b82c3ebb8f2485e0b7a40055b072193039d95dcb5d35fcba72c/pydantic_monty-0.0.17-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b5fcdcca45439844bee268686f37226dbf5803ec7a5945f5536c41419f151dac", size = 7768902, upload-time = "2026-04-22T20:14:29.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/7c/500a002f1a52f17c8b8a989875da3e1590c18ce95c89856aa967e2348a36/pydantic_monty-0.0.17-cp314-cp314-win32.whl", hash = "sha256:4dd3e6e80a415e7272f7a7583a4f8e045096653f6074e181117eb61fc8fe3b45", size = 7227049, upload-time = "2026-04-22T20:14:01.871Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/20/b8f552f2a863778f49ebb708f18aaf0bfb275480a48965786e06efacf1c4/pydantic_monty-0.0.17-cp314-cp314-win_amd64.whl", hash = "sha256:36a8090a628e8cf91df8f66c721a71050ac8f48473d4992b9afbd9585941a647", size = 8062999, upload-time = "2026-04-22T20:14:44.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/bf/152bbb3315dfa46d4e4aae71779230e50c67a34d859a3470fd75c01b795c/pydantic_monty-0.0.19-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b073e64edfd62cca918d792d6fe559512472f981f949e53a7aec673201f5f554", size = 2492733, upload-time = "2026-07-24T09:56:49.612Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/16/9d37f1bf94c47c593a06ecb918bb64a2c8a38af24d6fa2b0d0e031938edf/pydantic_monty-0.0.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5825ae6c40270166f0f31b6e62d59b0fe47201d12b1be5842efc22d3b7dadcee", size = 2234610, upload-time = "2026-07-24T09:56:51.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/4f/31732f4b9c2b6574eb564e420593b9be3f80d92440211970fab23e882bc5/pydantic_monty-0.0.19-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5b6821c0035ff2c02cf0f37823fb905890f7238ce923bbc3ca937740f9554836", size = 2303116, upload-time = "2026-07-24T09:56:52.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/2c/c21e6179361100dd8b9ad410df775d176a29e0b85f2ffc3144fd29840ee9/pydantic_monty-0.0.19-cp312-cp312-manylinux_2_28_armv7l.whl", hash = "sha256:5fb4514d99a10e304237a82baeb6072e934ce8462dcd5fb5c3fea0ee0ef16aeb", size = 2007947, upload-time = "2026-07-24T09:56:54.006Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/f0/ad64f4894334499f689bdce7e5b5dda6b680d98989424092dcbf21666564/pydantic_monty-0.0.19-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:2b0f154ac2e6450befa337a99a8519e2f1195cc59f88bd73d780067ace0c4c97", size = 2151468, upload-time = "2026-07-24T09:56:55.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/97/4ff5f9170e4865ec28fb152bc6ebaa8bd1873e695ee98af2103607ba42e8/pydantic_monty-0.0.19-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:6fcb4e3a312397432f7c5a0aa04a765670d9f37f4e23f37cb580b3faa2f218b1", size = 2300164, upload-time = "2026-07-24T09:56:57.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/b1/c9bfb6708bc5d9f6b040db46156c6e3f16de68ab22f07e2de4f25782414b/pydantic_monty-0.0.19-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:955d308171ba0260f75d2dede18c46d9732647f94b8f7fba3076bb539f155dc8", size = 2164984, upload-time = "2026-07-24T09:56:58.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/2d/8c1492216632f53e229cb2886c7fd09613d5990f4856f93f7ae0364da9bc/pydantic_monty-0.0.19-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3d7e1a6cc1977b24e01b5322888a5ba3f05b112a04a1646ba51f2c34c562a3d9", size = 2357823, upload-time = "2026-07-24T09:57:00.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/cc/ae4adaaf343de00748fc3598402c1523e48db7094a9c1e0fa26177699440/pydantic_monty-0.0.19-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4904785a34f71f59aa1eb6dc081bafd09e4caa2f028cd379aa3aa846b8a1c27d", size = 2497387, upload-time = "2026-07-24T09:57:01.686Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/09/eaef87ed6cbffed9c720dd3cb850e748b0aab11f5ec1ecffb56250973f7d/pydantic_monty-0.0.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce15a5326e24cf7045ec9c1456ddb92abd09df91708771c3ae5e72d8e6374c81", size = 2738391, upload-time = "2026-07-24T09:57:03.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/af/58be6fd6ea87e27bd57435013ca1f63d645a0c990c2f23708bcdd048af24/pydantic_monty-0.0.19-cp312-cp312-win32.whl", hash = "sha256:600eb259415e8b2dfef4be38d030c945b3fbb4fb85e727cb97131e7329ab017f", size = 1908274, upload-time = "2026-07-24T09:57:05.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/b7/1cb54e43113cb69c40fb765cfee3be1c222d81153b432131f50508569aee/pydantic_monty-0.0.19-cp312-cp312-win_amd64.whl", hash = "sha256:2c98b1c99994f92ab487a762b107067ab70036f64231e05aa3f6d2b16018688e", size = 2111335, upload-time = "2026-07-24T09:57:06.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/17/0926da051f34ccaa45bf528777dc99e5ea611669cdd7b715be1e086c1fe0/pydantic_monty-0.0.19-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:c01fb1162cf87dbf145b875450eabfde6b35b26f27ed63468398cb4c37732064", size = 2496669, upload-time = "2026-07-24T09:57:07.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/ba/c38f79e24971b4d2205ee156df6e5c6ccdcc720152f54a956cc26e7488b0/pydantic_monty-0.0.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e97dc97de32410003fb5517b72ab47799b4546a3ab48a75e60260cf73734da76", size = 2234599, upload-time = "2026-07-24T09:57:09.458Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/89/fb2ed1677ad2c3e2801aa119fdc280d1c64f3480e1015811e0942c9a7d20/pydantic_monty-0.0.19-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:06318e6add780f66259067829ab16062ce7a556dba0884372a881881b4a7c3bf", size = 2305696, upload-time = "2026-07-24T09:57:10.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ec/e36ff1c2c97f46420d57680199e7ae2e1eb306d2cfda22be2448e53e7484/pydantic_monty-0.0.19-cp313-cp313-manylinux_2_28_armv7l.whl", hash = "sha256:5a765a36141bbeb074d89b424d5488bed4e358c603c72606cc9d81ee44d83de2", size = 2007600, upload-time = "2026-07-24T09:57:12.333Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/88/b47670d3e28f99dc2f4c2686dc42233843dce1a607f3d3cc8a387f3b9b74/pydantic_monty-0.0.19-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:48f5ebc048779c854f993834586ba372df3b65500d1ed7f147023abc29aeb2c4", size = 2151382, upload-time = "2026-07-24T09:57:13.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/18/559d71fc66a769c22f6b2a8470515aa6e69a141ab617900fe28a806080b7/pydantic_monty-0.0.19-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2eb7502f93d8bb568d255fccca95e3b9daca05bb674b9c5323fcba14d088932c", size = 2302643, upload-time = "2026-07-24T09:57:15.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/b4/171be42e2ec211bd01fe4be7b6de9ab0c346081edc33830f9f9b781341ab/pydantic_monty-0.0.19-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:608994600a839dd863940ac84810c48d378390a4fea8c77e5145feb84037e610", size = 2169103, upload-time = "2026-07-24T09:57:16.805Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/ce/8c47253c8f3f528f0fa2d87dde85623dc3f211189a372e2d69cb6ad896b1/pydantic_monty-0.0.19-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:13a652f9dffa6d25ea458fec83108f60f29682caf42cecef91955b5b8cb04365", size = 2358155, upload-time = "2026-07-24T09:57:18.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/4e/239107b83bae3a20e4f5424f6c6f3f88bae1fd1e734603c298167985f8be/pydantic_monty-0.0.19-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6167c966db7d8d2fe03940f8da596de6dcd184dcf5cf6209f0a1a9af8792550d", size = 2500858, upload-time = "2026-07-24T09:57:19.878Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/55/02a7059f20e7198e511ac0b88a3957a2c01b8991326359b7c1bb590a09b8/pydantic_monty-0.0.19-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3102b44307d04f41897ae51d4daeab4fc9b5bf84a78c036781b488876ef998c3", size = 2742212, upload-time = "2026-07-24T09:57:21.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/28/b632fe0e8eeba3f2b4dbec6ebd4c9b569c20e9597007f2d0fbbf04101307/pydantic_monty-0.0.19-cp313-cp313-win32.whl", hash = "sha256:e43da52776796a894f40533a7e5a322e98d9aaf7d8f6fbb7dc21a0de60a93f41", size = 1908553, upload-time = "2026-07-24T09:57:22.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/d3/b90872f017871339ceb03e70fa4915ef8682128a476a66adffedfff874d8/pydantic_monty-0.0.19-cp313-cp313-win_amd64.whl", hash = "sha256:fd8c195875f8f44d55bc7d1d53c4e43248b184b3ac803d8131c92d4cc05a1aef", size = 2111345, upload-time = "2026-07-24T09:57:24.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/11/c2aed55502bfc9620837312f0e2fca7a3d4bc959824a66d81b72144d7256/pydantic_monty-0.0.19-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:2692dc4452937cf2200afd257297e9ac3ccff122b80aa7a69935e8275d684193", size = 2497017, upload-time = "2026-07-24T09:57:25.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/d0/d4b44a81c71308109cfa642a24b803057615ca1609530c5e66c376780efe/pydantic_monty-0.0.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5a4717f829b35c4bc5d9f6f52a52d19b90729bd494bcb69133bd4c0afcab9c76", size = 2247468, upload-time = "2026-07-24T09:57:27.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/89/52848dce3acbb1d58df34496db3c4718814cc39f6db01a5025bfdc12b530/pydantic_monty-0.0.19-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:99f7282213ad6ebf7daf149a88d1517e6328afbf6780180512b9de62f30d292b", size = 2306181, upload-time = "2026-07-24T09:57:29.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/05/f7791c79c7be2240c43a9287196ed49034f773e3f4158492f028e486ebc2/pydantic_monty-0.0.19-cp314-cp314-manylinux_2_28_armv7l.whl", hash = "sha256:978788b1c56fa0c49927e0a35151f0a635ef2f8d947d16915541ff864d2112f5", size = 2008738, upload-time = "2026-07-24T09:57:30.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/a1/d714258eeb2583acaab2035834acc54ac6baa65bac456f897d901bc0c03a/pydantic_monty-0.0.19-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:8b821cac39deeb1abb2994d5a65f117ce26e55c586d0f570d425ff469ff48e3c", size = 2152275, upload-time = "2026-07-24T09:57:31.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e5/cdb1fa761e992a489b07a17adb80c21bf99eabd1286ca62f9e73acda1f4b/pydantic_monty-0.0.19-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:b43c4ffa5651f0eca97458dc673d7952064ae5eb5b836d23967a7d41483bb8f4", size = 2303533, upload-time = "2026-07-24T09:57:33.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/9b/e6685cf82521e68e0dcb94e0c97a1a20410b1266fbce7008c0caa3484039/pydantic_monty-0.0.19-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:4054610601358943a3dd740a8d59a6812cc682e94d6903cb72baadae1ef5d2ac", size = 2169585, upload-time = "2026-07-24T09:57:34.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/f4/6f031a628d3de72d95bedbb18292ccf998d9a422aff58eedf37347a0b367/pydantic_monty-0.0.19-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0b2a34c320968a3cef3d933737e99c932f13c55667315128f366afdaeea2be04", size = 2375171, upload-time = "2026-07-24T09:57:35.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/d3/4367bccdf2c06a977c0d5ddf816190d570a07199f011034df16d51c84723/pydantic_monty-0.0.19-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ffaf950f4284bb193a54f18fa4e3e8c225b5b52265813abffe492074e90c65fb", size = 2501475, upload-time = "2026-07-24T09:57:37.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/0e/7a0e1d5c016848afc9a8605aa4f16e6960d68806e775865b986aacaf8f88/pydantic_monty-0.0.19-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:bb1e5ee6762f9494bfb0f4cc316ad3e9a8c7917e3c984a23eb2e2322d401e5cb", size = 2742547, upload-time = "2026-07-24T09:57:39.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/f0/92f77cf088f1df72a5dbab109c8c0e82f7ddeb18e65835a8dffcf967bb4a/pydantic_monty-0.0.19-cp314-cp314-win32.whl", hash = "sha256:b68ef6503b39f2f014162e3d8e7f48b9722a43ceb7b6d7199dc6d545f4c37b34", size = 1907832, upload-time = "2026-07-24T09:57:40.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/3e/85e1a914f81659ea25c34916fdef27fcda2b00323dafb76cf2a5321f7c11/pydantic_monty-0.0.19-cp314-cp314-win_amd64.whl", hash = "sha256:14ef37b43c5bf90966ca51bcad0fae892a3c2546cd151fadc039e0a25ca74073", size = 2123187, upload-time = "2026-07-24T09:57:42.352Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-monty-runtime"
|
||||
version = "0.0.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/29/e44460fd934584ddecc6b8a8acddbc0605e86ea9d027910acdbf526c8f14/pydantic_monty_runtime-0.0.19.tar.gz", hash = "sha256:717e11349d7234575750cec881ac390961de02a40bd09f875dc5e097705b896b", size = 1322628, upload-time = "2026-07-24T10:00:14.955Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/11/a6f4e12982b2232b9036db334fbcfecbacf46b9acaf311f9c3110e431c53/pydantic_monty_runtime-0.0.19-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:be34905548e31237fc683f5a34986489c127727e8f65481e8c87c4ad0b3a4dc2", size = 9449108, upload-time = "2026-07-24T09:58:44.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/a0/1e720b1bba457c6a1ab4fca20c571ae058238c7df3e1892b5efebad05a8b/pydantic_monty_runtime-0.0.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0f402f20672c1e25d4915de7e97023519461b9ffbedacd08c427d40daa77bdd6", size = 9735875, upload-time = "2026-07-24T09:58:46.952Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/97/4439b312d9463881630dd9d998d2c8fe2b8ef457b451202c71816e25688c/pydantic_monty_runtime-0.0.19-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9ec0d25dfbe65b9a5dfe77ff86353e95a86774d786a1d496206163f828f072fd", size = 9171496, upload-time = "2026-07-24T09:58:49.504Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/67/fa7b99afe1917b89eec3b4b6375f468c76eeea8227dd48361b7f2db90d97/pydantic_monty_runtime-0.0.19-cp312-cp312-manylinux_2_28_armv7l.whl", hash = "sha256:7ecc2f8eeabf6483908db4cc69b7d4c156a95675dcbdda2a7540a22ed904bfb2", size = 9565495, upload-time = "2026-07-24T09:58:51.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/a6/83a9dceb8d9f5dffd9a082b60590bb61b0ac48dca19aa02847ebbab1ad46/pydantic_monty_runtime-0.0.19-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:fc1e508b9dc2cab64e27004d0f4ce44c7e1d255e85bafba390884fa07d696319", size = 10199598, upload-time = "2026-07-24T09:58:54.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/5e/bafd9dfa9a9a0d26b9882053aaea2280d791225c1e3b33b4b48f23fd5f92/pydantic_monty_runtime-0.0.19-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:9e3f6e366f62e54d7bd0fb995f007f42d23c26eee560d57c04d75d5adde3b45c", size = 10355698, upload-time = "2026-07-24T09:58:56.713Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/00/ef55cdc9f5ade20475622bb8dba617efce00ef9da2b94b36a76172edadce/pydantic_monty_runtime-0.0.19-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:2ddf4f2d1d063f330bb54b2dc00f6d5bcfbdd3defdc5988b856d350f62160e26", size = 10197859, upload-time = "2026-07-24T09:58:59.158Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/67/d1c359658458cf97296fda4359bfc71de8c9f968fb3db68eb7ce00136d43/pydantic_monty_runtime-0.0.19-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ce2c9550c30a5c94b63dfe578243d0823511feeecb020723f5140f9737505410", size = 10661715, upload-time = "2026-07-24T09:59:01.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/1f/9841d93f956bfbd0bbbac75edf42bb3b13b5bad7d25b09d9a221b5e54d71/pydantic_monty_runtime-0.0.19-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:05563e178b6be783de088abe4a25cef9cfbc04811f7c1e1873860252e711edac", size = 9143212, upload-time = "2026-07-24T09:59:04.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ce/0cd3643cc5051456b7baad6f16d85fb1d05daefd0556e4c82ea8f22cc9a2/pydantic_monty_runtime-0.0.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de74aa4df47147d104bccdac4b39c1f3fd543091be3fd0156f77eeea3e483bc3", size = 9731590, upload-time = "2026-07-24T09:59:06.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/5a/69f4eabec2538df364242395ab3ef77b30a124a0e4b461231589651a1e97/pydantic_monty_runtime-0.0.19-cp312-cp312-win32.whl", hash = "sha256:5208056d9e23d951768ba4b94df3caf7fd84bfe951f68ec4a1803eb03377bbeb", size = 9227834, upload-time = "2026-07-24T09:59:08.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/4b/221a21f477aef0c488cbe1467111b0988658bc4a42cfc6b404201bc432af/pydantic_monty_runtime-0.0.19-cp312-cp312-win_amd64.whl", hash = "sha256:e4bba0c6024a3a8bd8c8a8cba25233a19cf686218e97afb4c059aa0c625a4b8b", size = 10941520, upload-time = "2026-07-24T09:59:10.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/a8/1ba497c35eca33273f2144b8e78d94832cc33aec5f69d8bef56968a61933/pydantic_monty_runtime-0.0.19-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:84dd041652581335503af7c60ee7362a462d946a62e8c4a44a939984034d0d25", size = 9449108, upload-time = "2026-07-24T09:59:13.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/6e/08c05c9729a35d6c9831bfd57d535bd1257f601d15b62936c27f1c0cfb47/pydantic_monty_runtime-0.0.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:263ab33dce2008ca6c83b4013b0280a98a1991d4071c55413d00b539940fe8aa", size = 9735874, upload-time = "2026-07-24T09:59:15.918Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/64/63fbdb4c069ceb2af6261fe5923864b5be309799087f16a5dccc96141c12/pydantic_monty_runtime-0.0.19-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:64f56d48097d8a158125534b20e8b22003c5e727082a303907da8e61aae0c7e3", size = 9171498, upload-time = "2026-07-24T09:59:18.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/cf/82390fe7f0e1100662e6cac7a1c0de716ea4bf851a53aa2b0ef324fc4e3e/pydantic_monty_runtime-0.0.19-cp313-cp313-manylinux_2_28_armv7l.whl", hash = "sha256:a4d80fb4598524cd5039a596f8e6900adb2a5a2da66d012a3734a2b441ad06c3", size = 9565494, upload-time = "2026-07-24T09:59:21.232Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/37/caa8bf32ba0c0e8ce31ef2773b1a1f60d688e0237cd396e48bbef9f7161f/pydantic_monty_runtime-0.0.19-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:1c5cd0b140c765772e0606a7047fbf95cc49d62d0d8b79b4f520dae0e38b3ba7", size = 10199597, upload-time = "2026-07-24T09:59:23.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/25/ae9bb52518b2ce8fe7509d6cad4f4916b4458b748fecb180bef2d78165e9/pydantic_monty_runtime-0.0.19-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:7d80975c6e792088285f49a91e26de483ef95e5bffc4939b02d4c5ea1059749b", size = 10355699, upload-time = "2026-07-24T09:59:26.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/38/0875b1239b8ea57e4ea6de421cd240fc5a88c02e381f88d858f00439b1e9/pydantic_monty_runtime-0.0.19-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:f826cb911f78fe4fddf09fa3f38518484041908d08de927fa7dd134c002a2c77", size = 10197858, upload-time = "2026-07-24T09:59:28.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/d4/9365473fe31e53a6dc4d6fea406d5d8f3f03a9b7d84d1f29a9e6d664c862/pydantic_monty_runtime-0.0.19-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:fdf56c6cd8c6163737d1ce284f9acb00feff77b7e952f041dc281b94336c4fc9", size = 10661715, upload-time = "2026-07-24T09:59:31.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/f9/8d85b8f77d4006a8d80517a0781c49e6ca026f68747f801b63298e64197e/pydantic_monty_runtime-0.0.19-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a675f165773af0a2e0209dd96014aa9e613115cdc6dad297a7bb96cf87f83315", size = 9143210, upload-time = "2026-07-24T09:59:33.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/53/ded73742ee9fa2160c711141c28ea2ee083f073019e89724049c5e67cd84/pydantic_monty_runtime-0.0.19-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9e42ab0287c0497d9d5529e8a53dd2f63f9d1f92f6592170baab894ea9956da6", size = 9731590, upload-time = "2026-07-24T09:59:36.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/06/67be8320dc592b8c4caa8c4c1544c8ddf2760029d46843d078aa5a4cdb14/pydantic_monty_runtime-0.0.19-cp313-cp313-win32.whl", hash = "sha256:1f5ff1b9585e648304096705045fb6bd90d43b561568b0d373265e8d201b1234", size = 9227833, upload-time = "2026-07-24T09:59:39.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/f4/bab34897974d83640f8773f03d2001142bc13e80e517bfce8c8c4a57157e/pydantic_monty_runtime-0.0.19-cp313-cp313-win_amd64.whl", hash = "sha256:7181a2153ff257fe34109685148167b6e8219d4acfe8f346115b58152fd26aa8", size = 10941519, upload-time = "2026-07-24T09:59:41.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/8d/f46ac4778b2ac64183607bc63ecc783f16ca9981de30eb8ec9aa5e7132cd/pydantic_monty_runtime-0.0.19-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4cc92139b476469c0d7e5caa147d38c2929de39bf1724cb22bbab80297823963", size = 9449107, upload-time = "2026-07-24T09:59:44.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/14/34a7bb4630d1bac0d055049568ecc888a87954c176428bde5764a7ff6ed7/pydantic_monty_runtime-0.0.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7a97e00bd46305f34a85f2e2e3ac4c92dbd3340e7af694aafb192ff58c4fb40e", size = 9735874, upload-time = "2026-07-24T09:59:46.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/b7/977de0b258c0858f52b23bd32afbfdf8a8c4614daff5d0b7d5c86332ce6e/pydantic_monty_runtime-0.0.19-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:46ad28b1b3c41113c9e89373da18bcc883a24da371d4eeb9b32d3d194286f1a5", size = 9171497, upload-time = "2026-07-24T09:59:48.768Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/39/4374200ee4b938fc8b5026056f13bb677e15796bca1323a16210738431ad/pydantic_monty_runtime-0.0.19-cp314-cp314-manylinux_2_28_armv7l.whl", hash = "sha256:dc61844187a32c2f9c2b69846c5d55679eb838b4f7e495b4d03509f89fbf41f9", size = 9565495, upload-time = "2026-07-24T09:59:51.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/55/c4ee4b0a10610359e09cad9d327db19095914a3bf427fb3d8164ec2bcae0/pydantic_monty_runtime-0.0.19-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:b23b52a79ee6be0a943e4b8e5d996e6c489a37b23a337838164f22c9e8ed11a7", size = 10199598, upload-time = "2026-07-24T09:59:53.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/62/cc9df084e9f930bbb2873b6dd832b377f76071aec309b1545589d818fd90/pydantic_monty_runtime-0.0.19-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:484496817f5f238c42aaea8a1945b545a17d8ef2a21e9e81799d55e481d25485", size = 10355699, upload-time = "2026-07-24T09:59:56.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/cc/bb13e6f655fcaee340032ad6a3cd1524957d0fa471ddc7e27bdb1f4c240b/pydantic_monty_runtime-0.0.19-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:c3e7cbad58ae9bd3402581faa7d54d719f9c140dc1888f5c9439d45bff528ea1", size = 10197859, upload-time = "2026-07-24T09:59:58.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/c3/c532715987383668ee835337e1485f51585bc8bf189f033370a05eff17f1/pydantic_monty_runtime-0.0.19-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:5534067dc7ffdae809293da95d0e95b6d8481f4c88aff59385e19f466ba3c0f0", size = 10661715, upload-time = "2026-07-24T10:00:01.163Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/6c/9a0ab28a061efc184398a0b4db34e459572bb2316cf766f0d6ce65b475e3/pydantic_monty_runtime-0.0.19-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c1ae32b06a4456ab223bafa54d29a349066d625d09063c28ba14ee9019f9b7c2", size = 9143211, upload-time = "2026-07-24T10:00:03.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/5f/af5b3e6395834572975d98f4d1a00a57ee8029bf68ca5732347550f32b35/pydantic_monty_runtime-0.0.19-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:497cf8c3f30992f9aafc8707084eaffe2391b7b5dec067d04d5715f9a562c56b", size = 9731591, upload-time = "2026-07-24T10:00:06.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/98/96797fd269342cfdb49f03c22fd9a05ef91f711089081c4b3861b9c520e2/pydantic_monty_runtime-0.0.19-cp314-cp314-win32.whl", hash = "sha256:942feb948df8edb61ae7ba6ae77dc655e6985be06d72eef886562fe573ba3086", size = 9227832, upload-time = "2026-07-24T10:00:08.708Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/fc/02d15281c8e00b48df9af8f75a4fe06f3f8f33ef6a910507a45a19f2b61b/pydantic_monty_runtime-0.0.19-cp314-cp314-win_amd64.whl", hash = "sha256:91d93339c70483ed9256b3b15e3375f6597ae65be280f9b89ba9ca0355f95f54", size = 10941519, upload-time = "2026-07-24T10:00:11.226Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue