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:
parent
75da6871ba
commit
da76b6004e
4 changed files with 505 additions and 0 deletions
|
|
@ -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
171
scripts/import_map.py
Executable 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
285
tests/test_import_map.py
Normal 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
48
uv.lock
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue