Add YAML map import script with tests

Implements import_map.py script that converts YAML zone definitions to
TOML format used by the engine. YAML format supports all zone features
including terrain, portals, spawns, ambient messages, and boundaries.
This commit is contained in:
Jared Miller 2026-02-14 12:32:18 -05:00
parent 75da6871ba
commit da76b6004e
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
4 changed files with 505 additions and 0 deletions

View file

@ -6,6 +6,7 @@ requires-python = ">=3.12"
dependencies = [ dependencies = [
"telnetlib3 @ file:///home/jtm/src/telnetlib3", "telnetlib3 @ file:///home/jtm/src/telnetlib3",
"pygments>=2.17.0", "pygments>=2.17.0",
"pyyaml>=6.0",
] ]
[dependency-groups] [dependency-groups]

171
scripts/import_map.py Executable file
View file

@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""Import YAML zone definitions and convert to TOML format.
Usage:
python scripts/import_map.py path/to/zone.yaml
python scripts/import_map.py path/to/zone.yaml --output content/zones/
"""
from __future__ import annotations
import argparse
from pathlib import Path
import yaml
def import_map(yaml_path: Path, output_dir: Path) -> Path:
"""Import a YAML zone definition and convert to TOML.
Args:
yaml_path: Path to YAML file
output_dir: Directory where TOML file should be written
Returns:
Path to created TOML file
Raises:
KeyError: If required fields are missing
"""
# Load YAML
with open(yaml_path) as f:
data = yaml.safe_load(f)
# Required fields
name = data["name"]
width = data["width"]
height = data["height"]
# Optional fields with defaults
description = data.get("description", "")
toroidal = data.get("toroidal", True)
safe = data.get("safe", False)
# Handle spawn coordinates
spawn = data.get("spawn", [0, 0])
spawn_x = spawn[0]
spawn_y = spawn[1]
# Build TOML lines
lines = []
# Basic fields
escaped_name = name.replace('"', '\\"')
lines.append(f'name = "{escaped_name}"')
if description:
escaped_description = description.replace('"', '\\"')
lines.append(f'description = "{escaped_description}"')
lines.append(f"width = {width}")
lines.append(f"height = {height}")
lines.append(f"toroidal = {str(toroidal).lower()}")
lines.append(f"spawn_x = {spawn_x}")
lines.append(f"spawn_y = {spawn_y}")
if safe:
lines.append("safe = true")
lines.append("")
# Terrain section
terrain_data = data.get("terrain", {})
rows = terrain_data.get("rows", [])
lines.append("[terrain]")
lines.append("rows = [")
for row in rows:
lines.append(f' "{row}",')
lines.append("]")
lines.append("")
# Impassable tiles
lines.append("[terrain.impassable]")
impassable = terrain_data.get("impassable", [])
if impassable:
tiles_str = ", ".join(f'"{tile}"' for tile in impassable)
lines.append(f"tiles = [{tiles_str}]")
else:
lines.append('tiles = ["^", "~"]') # default from zones.py
lines.append("")
# Ambient messages
ambient_data = data.get("ambient", {})
if ambient_data:
messages = ambient_data.get("messages", [])
interval = ambient_data.get("interval", 120)
lines.append("[ambient]")
lines.append(f"interval = {interval}")
lines.append("messages = [")
for msg in messages:
# Escape quotes in messages
escaped_msg = msg.replace('"', '\\"')
lines.append(f' "{escaped_msg}",')
lines.append("]")
lines.append("")
# Portals
portals = data.get("portals", [])
for portal in portals:
lines.append("[[portals]]")
lines.append(f"x = {portal['x']}")
lines.append(f"y = {portal['y']}")
escaped_target = portal["target"].replace('"', '\\"')
lines.append(f'target = "{escaped_target}"')
escaped_label = portal["label"].replace('"', '\\"')
lines.append(f'label = "{escaped_label}"')
if "aliases" in portal:
aliases_str = ", ".join(f'"{alias}"' for alias in portal["aliases"])
lines.append(f"aliases = [{aliases_str}]")
lines.append("")
# Spawn rules
spawns = data.get("spawns", [])
for spawn in spawns:
lines.append("[[spawns]]")
lines.append(f'mob = "{spawn["mob"]}"')
lines.append(f"max_count = {spawn.get('max_count', 1)}")
lines.append(f"respawn_seconds = {spawn.get('respawn_seconds', 300)}")
if "home_region" in spawn:
hr = spawn["home_region"]
lines.append(
f"home_region = {{ x = [{hr['x'][0]}, {hr['x'][1]}], "
f"y = [{hr['y'][0]}, {hr['y'][1]}] }}"
)
lines.append("")
# Write TOML file
toml_content = "\n".join(lines)
output_path = output_dir / f"{name}.toml"
output_path.write_text(toml_content)
return output_path
def main() -> None:
"""CLI entry point."""
parser = argparse.ArgumentParser(
description="Import YAML zone definition and convert to TOML"
)
parser.add_argument("yaml_file", type=Path, help="Path to YAML zone file")
parser.add_argument(
"--output",
type=Path,
default=Path("content/zones"),
help="Output directory (default: content/zones)",
)
args = parser.parse_args()
yaml_path = args.yaml_file
output_dir = args.output
if not yaml_path.exists():
print(f"Error: {yaml_path} does not exist")
return
output_dir.mkdir(parents=True, exist_ok=True)
output_file = import_map(yaml_path, output_dir)
print(f"Imported {yaml_path} -> {output_file}")
if __name__ == "__main__":
main()

285
tests/test_import_map.py Normal file
View file

@ -0,0 +1,285 @@
"""Tests for YAML map import script."""
from __future__ import annotations
from pathlib import Path
import pytest
from mudlib.portal import Portal
from mudlib.zones import load_zone
def test_minimal_yaml_import(tmp_path: Path) -> None:
"""Test importing a minimal YAML map."""
yaml_content = """
name: test_zone
width: 5
height: 5
terrain:
rows:
- "....."
- "....."
- "....."
- "....."
- "....."
"""
yaml_file = tmp_path / "test_zone.yaml"
yaml_file.write_text(yaml_content)
# Import the script module
from scripts.import_map import import_map
output_file = import_map(yaml_file, tmp_path)
# Verify TOML was created
assert output_file.exists()
assert output_file.name == "test_zone.toml"
# Verify it can be loaded by existing zone loader
zone = load_zone(output_file)
assert zone.name == "test_zone"
assert zone.width == 5
assert zone.height == 5
assert zone.description == ""
assert zone.toroidal is True # default
assert zone.spawn_x == 0 # default
assert zone.spawn_y == 0 # default
assert zone.safe is False # default
assert len(zone.terrain) == 5
assert all(len(row) == 5 for row in zone.terrain)
def test_full_featured_yaml_import(tmp_path: Path) -> None:
"""Test importing a YAML map with all features."""
yaml_content = """
name: complex_zone
description: a complex test zone
width: 10
height: 10
toroidal: false
spawn: [5, 5]
safe: true
terrain:
rows:
- "##########"
- "#........#"
- "#........#"
- "#........#"
- "#........#"
- "#........#"
- "#........#"
- "#........#"
- "#........#"
- "##########"
impassable: ["#"]
ambient:
interval: 60
messages:
- "wind howls..."
- "a distant sound"
portals:
- x: 5
y: 0
target: "other_zone:10,10"
label: "a wide path"
aliases: ["path", "wide"]
spawns:
- mob: goblin
max_count: 3
respawn_seconds: 300
home_region:
x: [1, 9]
y: [1, 9]
"""
yaml_file = tmp_path / "complex_zone.yaml"
yaml_file.write_text(yaml_content)
from scripts.import_map import import_map
output_file = import_map(yaml_file, tmp_path)
# Load and verify all fields
zone = load_zone(output_file)
assert zone.name == "complex_zone"
assert zone.description == "a complex test zone"
assert zone.width == 10
assert zone.height == 10
assert zone.toroidal is False
assert zone.spawn_x == 5
assert zone.spawn_y == 5
assert zone.safe is True
# Verify terrain
assert len(zone.terrain) == 10
assert zone.terrain[0] == list("##########")
assert zone.terrain[1] == list("#........#")
# Verify impassable
assert "#" in zone.impassable
# Verify ambient messages
assert zone.ambient_messages == ["wind howls...", "a distant sound"]
assert zone.ambient_interval == 60
# Verify portals
portals = [obj for obj in zone._contents if isinstance(obj, Portal)]
assert len(portals) == 1
portal = portals[0]
assert portal.x == 5
assert portal.y == 0
assert portal.target_zone == "other_zone"
assert portal.target_x == 10
assert portal.target_y == 10
assert portal.name == "a wide path"
assert portal.aliases == ["path", "wide"]
# Verify spawn rules
assert len(zone.spawn_rules) == 1
spawn = zone.spawn_rules[0]
assert spawn.mob == "goblin"
assert spawn.max_count == 3
assert spawn.respawn_seconds == 300
assert spawn.home_region == {"x": [1, 9], "y": [1, 9]}
def test_missing_required_field_raises_error(tmp_path: Path) -> None:
"""Test that missing required fields raise appropriate errors."""
yaml_content = """
width: 5
height: 5
"""
yaml_file = tmp_path / "incomplete.yaml"
yaml_file.write_text(yaml_content)
from scripts.import_map import import_map
with pytest.raises(KeyError, match="name"):
import_map(yaml_file, tmp_path)
def test_spawn_list_format(tmp_path: Path) -> None:
"""Test that spawn coordinates as [x, y] list are parsed correctly."""
yaml_content = """
name: spawn_test
width: 3
height: 3
spawn: [1, 2]
terrain:
rows:
- "..."
- "..."
- "..."
"""
yaml_file = tmp_path / "spawn_test.yaml"
yaml_file.write_text(yaml_content)
from scripts.import_map import import_map
output_file = import_map(yaml_file, tmp_path)
zone = load_zone(output_file)
assert zone.spawn_x == 1
assert zone.spawn_y == 2
def test_defaults_applied(tmp_path: Path) -> None:
"""Test that sensible defaults are applied for optional fields."""
yaml_content = """
name: defaults_test
width: 3
height: 3
terrain:
rows:
- "..."
- "..."
- "..."
"""
yaml_file = tmp_path / "defaults_test.yaml"
yaml_file.write_text(yaml_content)
from scripts.import_map import import_map
output_file = import_map(yaml_file, tmp_path)
zone = load_zone(output_file)
# Verify defaults
assert zone.toroidal is True
assert zone.spawn_x == 0
assert zone.spawn_y == 0
assert zone.safe is False
assert zone.ambient_messages == []
assert zone.spawn_rules == []
def test_multiple_portals(tmp_path: Path) -> None:
"""Test importing multiple portals."""
yaml_content = """
name: multi_portal
width: 5
height: 5
terrain:
rows:
- "....."
- "....."
- "....."
- "....."
- "....."
portals:
- x: 0
y: 0
target: "zone1:5,5"
label: "north path"
- x: 4
y: 4
target: "zone2:0,0"
label: "south path"
"""
yaml_file = tmp_path / "multi_portal.yaml"
yaml_file.write_text(yaml_content)
from scripts.import_map import import_map
output_file = import_map(yaml_file, tmp_path)
zone = load_zone(output_file)
portals = [obj for obj in zone._contents if isinstance(obj, Portal)]
assert len(portals) == 2
def test_multiple_spawns(tmp_path: Path) -> None:
"""Test importing multiple spawn rules."""
yaml_content = """
name: multi_spawn
width: 5
height: 5
terrain:
rows:
- "....."
- "....."
- "....."
- "....."
- "....."
spawns:
- mob: goblin
max_count: 2
- mob: orc
max_count: 1
respawn_seconds: 600
"""
yaml_file = tmp_path / "multi_spawn.yaml"
yaml_file.write_text(yaml_content)
from scripts.import_map import import_map
output_file = import_map(yaml_file, tmp_path)
zone = load_zone(output_file)
assert len(zone.spawn_rules) == 2
assert zone.spawn_rules[0].mob == "goblin"
assert zone.spawn_rules[0].max_count == 2
assert zone.spawn_rules[1].mob == "orc"
assert zone.spawn_rules[1].respawn_seconds == 600

48
uv.lock
View file

@ -109,6 +109,7 @@ version = "0.1.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "pygments" }, { name = "pygments" },
{ name = "pyyaml" },
{ name = "telnetlib3" }, { name = "telnetlib3" },
] ]
@ -124,6 +125,7 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "pygments", specifier = ">=2.17.0" }, { name = "pygments", specifier = ">=2.17.0" },
{ name = "pyyaml", specifier = ">=6.0" },
{ name = "telnetlib3", directory = "../../src/telnetlib3" }, { name = "telnetlib3", directory = "../../src/telnetlib3" },
] ]
@ -218,6 +220,52 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" },
] ]
[[package]]
name = "pyyaml"
version = "6.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.15.0" version = "0.15.0"