mirror of
https://github.com/p-e-w/heretic.git
synced 2026-08-30 17:06:01 -07:00
fix: validate scorer instance names in config (#407)
* fix: validate scorer instance names in config * fix: run scorer config tests in CI * fix: drop redundant UV_PYTHON env and quote unittest pattern * fix: report precise scorer name validation errors
This commit is contained in:
@@ -43,7 +43,9 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
env:
|
env:
|
||||||
PYTHONUNBUFFERED: "1"
|
PYTHONUNBUFFERED: "1"
|
||||||
run: uv run tests/run_tests.py 2>&1
|
run: |
|
||||||
|
uv run python -m unittest discover -s tests -p 'test_*.py'
|
||||||
|
uv run tests/run_tests.py 2>&1
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
run: uv build
|
run: uv build
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from pydantic import (
|
|||||||
Field,
|
Field,
|
||||||
NonNegativeInt,
|
NonNegativeInt,
|
||||||
PositiveInt,
|
PositiveInt,
|
||||||
|
field_validator,
|
||||||
)
|
)
|
||||||
from pydantic_settings import (
|
from pydantic_settings import (
|
||||||
BaseSettings,
|
BaseSettings,
|
||||||
@@ -123,6 +124,23 @@ class ScorerConfig(BaseModel):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@field_validator("instance_name")
|
||||||
|
@classmethod
|
||||||
|
def validate_instance_name(cls, value: str | None) -> str | None:
|
||||||
|
if value is None:
|
||||||
|
return value
|
||||||
|
|
||||||
|
if not value.strip():
|
||||||
|
raise ValueError("cannot be empty or whitespace")
|
||||||
|
|
||||||
|
if "." in value:
|
||||||
|
raise ValueError("'.' is not allowed")
|
||||||
|
|
||||||
|
if any(char.isspace() for char in value):
|
||||||
|
raise ValueError("whitespace is not allowed")
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class BenchmarkSpecification(BaseModel):
|
class BenchmarkSpecification(BaseModel):
|
||||||
task: str = Field(
|
task: str = Field(
|
||||||
|
|||||||
@@ -67,18 +67,6 @@ class Evaluator:
|
|||||||
# Instantiate scorers.
|
# Instantiate scorers.
|
||||||
instance_name = config.instance_name or None
|
instance_name = config.instance_name or None
|
||||||
|
|
||||||
if instance_name is not None:
|
|
||||||
if not instance_name.strip():
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid instance_name {instance_name} for scorer {scorer_cls.__name__}: "
|
|
||||||
"cannot be empty or whitespace"
|
|
||||||
)
|
|
||||||
if "." in instance_name or " " in instance_name:
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid instance_name {instance_name} for scorer {scorer_cls.__name__}: "
|
|
||||||
"'.' and whitespace are not allowed"
|
|
||||||
)
|
|
||||||
|
|
||||||
raw_settings = self._get_scorer_settings_raw(
|
raw_settings = self._get_scorer_settings_raw(
|
||||||
scorer_cls=scorer_cls, instance_name=instance_name
|
scorer_cls=scorer_cls, instance_name=instance_name
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# Copyright (C) 2025-2026 Philipp Emanuel Weidmann <pew@worldwidemann.com> + contributors
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
from heretic.config import ScorerConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ScorerConfigTests(unittest.TestCase):
|
||||||
|
def test_accepts_slug_like_instance_name(self) -> None:
|
||||||
|
config = ScorerConfig(
|
||||||
|
plugin="heretic.scorers.keyword_rate.KeywordRate",
|
||||||
|
optimization="minimize",
|
||||||
|
instance_name="small-1",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(config.instance_name, "small-1")
|
||||||
|
|
||||||
|
def test_rejects_empty_instance_name(self) -> None:
|
||||||
|
with self.assertRaises(ValidationError):
|
||||||
|
ScorerConfig(
|
||||||
|
plugin="heretic.scorers.keyword_rate.KeywordRate",
|
||||||
|
optimization="minimize",
|
||||||
|
instance_name=" \t",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rejects_whitespace_in_instance_name(self) -> None:
|
||||||
|
for instance_name in ["small name", "small\tname", "small\nname"]:
|
||||||
|
with self.subTest(instance_name=instance_name):
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
ValidationError, "whitespace is not allowed"
|
||||||
|
):
|
||||||
|
ScorerConfig(
|
||||||
|
plugin="heretic.scorers.keyword_rate.KeywordRate",
|
||||||
|
optimization="minimize",
|
||||||
|
instance_name=instance_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rejects_dot_in_instance_name(self) -> None:
|
||||||
|
with self.assertRaisesRegex(ValidationError, "'\\.' is not allowed"):
|
||||||
|
ScorerConfig(
|
||||||
|
plugin="heretic.scorers.keyword_rate.KeywordRate",
|
||||||
|
optimization="minimize",
|
||||||
|
instance_name="small.name",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user