Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
43f6b61a72 | |||
4d92935fda | |||
bf69d7463e | |||
6d68faf73c | |||
3e8bdfbc59 |
@ -8,6 +8,7 @@ import random
|
||||
import re
|
||||
|
||||
DICE_DESCRIPTOR_PATTERN = re.compile(r"^([0-9]*d[0-9]+)([+-]([0-9]+|[0-9]*d[0-9]+))*$")
|
||||
D666_COMBINATIONS = [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
|
||||
|
||||
|
||||
class Die:
|
||||
@ -96,3 +97,26 @@ class Die:
|
||||
result += self.modifier
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def d666(strict_order: bool = False) -> list[int]:
|
||||
"""Roll three six sided dice and use them as separate digits
|
||||
|
||||
“d666” is a special die type often used in random tables; here you don’t roll one 666 sided die, but three 6-sided
|
||||
ones, then use the three values of the three digits of the final result.
|
||||
|
||||
:param strict_order: if ``True``, use the dics in strict order. Otherwise, return all possible combinations.
|
||||
:returns: the list of all valid combinations
|
||||
"""
|
||||
|
||||
roll_values = [random.randint(1, 6) for _ in range(3)]
|
||||
results = []
|
||||
|
||||
combinations = [(0, 1, 2)] if strict_order else D666_COMBINATIONS
|
||||
|
||||
for a, b, c in combinations:
|
||||
result = roll_values[a] * 100 + roll_values[b] * 10 + roll_values[c]
|
||||
if result not in results:
|
||||
results.append(result)
|
||||
|
||||
return results
|
||||
|
192
gm_assistant/fate_chart.py
Normal file
192
gm_assistant/fate_chart.py
Normal file
@ -0,0 +1,192 @@
|
||||
# SPDX-FileCopyrightText: 2025 2025
|
||||
# SPDX-FileContributor: Gergely Polonkai
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Calculate the odds using Mythic RPG’s Fate Chart"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from .dice import Die
|
||||
|
||||
|
||||
class FateOutcome(Enum):
|
||||
"""Possible outcomes"""
|
||||
|
||||
EXC_YES = "eyes"
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
EXC_NO = "eno"
|
||||
|
||||
|
||||
class FateOdds(Enum):
|
||||
"""Odds of a “yes” answer"""
|
||||
|
||||
IMPOSSIBLE = 0
|
||||
NO_WAY = 1
|
||||
VERY_UNLIKELY = 2
|
||||
UNLIKELY = 3
|
||||
FIFTY_FIFTY = 4
|
||||
SOMEWHAT_LIKELY = 5
|
||||
LIKELY = 6
|
||||
VERY_LIKELY = 7
|
||||
NEAR_SURE_THING = 8
|
||||
SURE_THING = 9
|
||||
HAS_TO_BE = 10
|
||||
HAS_TO_BE2 = 11
|
||||
|
||||
|
||||
TABLE: dict[FateOdds, list[tuple[int, int, int]]] = {
|
||||
FateOdds.IMPOSSIBLE: [
|
||||
(0, -20, 77),
|
||||
(0, 0, 81),
|
||||
(0, 0, 81),
|
||||
(1, 5, 82),
|
||||
(1, 5, 82),
|
||||
(2, 10, 83),
|
||||
(3, 15, 84),
|
||||
(5, 25, 86),
|
||||
(10, 50, 91),
|
||||
],
|
||||
FateOdds.NO_WAY: [
|
||||
(0, 0, 81),
|
||||
(1, 5, 82),
|
||||
(1, 5, 82),
|
||||
(2, 10, 83),
|
||||
(3, 15, 84),
|
||||
(5, 25, 86),
|
||||
(7, 35, 88),
|
||||
(10, 50, 91),
|
||||
(15, 75, 96),
|
||||
],
|
||||
FateOdds.VERY_UNLIKELY: [
|
||||
(1, 5, 82),
|
||||
(1, 5, 82),
|
||||
(2, 10, 83),
|
||||
(3, 15, 84),
|
||||
(5, 25, 86),
|
||||
(9, 45, 90),
|
||||
(10, 50, 91),
|
||||
(13, 65, 94),
|
||||
(16, 85, 97),
|
||||
],
|
||||
FateOdds.UNLIKELY: [
|
||||
(1, 5, 82),
|
||||
(2, 10, 83),
|
||||
(3, 15, 84),
|
||||
(4, 20, 85),
|
||||
(7, 35, 88),
|
||||
(10, 50, 91),
|
||||
(11, 55, 92),
|
||||
(15, 75, 96),
|
||||
(18, 90, 99),
|
||||
],
|
||||
FateOdds.FIFTY_FIFTY: [
|
||||
(2, 10, 83),
|
||||
(3, 15, 84),
|
||||
(5, 25, 86),
|
||||
(7, 35, 88),
|
||||
(10, 50, 91),
|
||||
(13, 65, 94),
|
||||
(15, 75, 96),
|
||||
(16, 85, 97),
|
||||
(19, 95, 100),
|
||||
],
|
||||
FateOdds.SOMEWHAT_LIKELY: [
|
||||
(4, 20, 85),
|
||||
(5, 25, 86),
|
||||
(9, 45, 90),
|
||||
(10, 50, 91),
|
||||
(13, 65, 94),
|
||||
(16, 80, 97),
|
||||
(16, 85, 97),
|
||||
(18, 90, 99),
|
||||
(19, 95, 100),
|
||||
],
|
||||
FateOdds.LIKELY: [
|
||||
(5, 25, 86),
|
||||
(7, 35, 88),
|
||||
(10, 50, 91),
|
||||
(11, 55, 92),
|
||||
(15, 75, 96),
|
||||
(16, 85, 97),
|
||||
(18, 90, 99),
|
||||
(19, 95, 100),
|
||||
(20, 100, 0),
|
||||
],
|
||||
FateOdds.VERY_LIKELY: [
|
||||
(9, 45, 90),
|
||||
(10, 50, 91),
|
||||
(13, 65, 94),
|
||||
(15, 75, 96),
|
||||
(16, 85, 97),
|
||||
(18, 90, 99),
|
||||
(19, 95, 100),
|
||||
(19, 95, 100),
|
||||
(21, 105, 0),
|
||||
],
|
||||
FateOdds.NEAR_SURE_THING: [
|
||||
(10, 50, 91),
|
||||
(11, 55, 92),
|
||||
(15, 75, 96),
|
||||
(16, 80, 97),
|
||||
(18, 90, 99),
|
||||
(19, 95, 100),
|
||||
(19, 95, 100),
|
||||
(20, 100, 0),
|
||||
(23, 115, 0),
|
||||
],
|
||||
FateOdds.SURE_THING: [
|
||||
(11, 55, 92),
|
||||
(13, 65, 94),
|
||||
(16, 80, 97),
|
||||
(16, 85, 97),
|
||||
(18, 90, 99),
|
||||
(19, 95, 100),
|
||||
(19, 95, 100),
|
||||
(22, 110, 0),
|
||||
(25, 125, 0),
|
||||
],
|
||||
FateOdds.HAS_TO_BE: [
|
||||
(16, 80, 97),
|
||||
(16, 85, 97),
|
||||
(18, 90, 99),
|
||||
(19, 95, 100),
|
||||
(19, 95, 100),
|
||||
(20, 100, 0),
|
||||
(20, 100, 0),
|
||||
(26, 130, 0),
|
||||
(26, 145, 0),
|
||||
],
|
||||
FateOdds.HAS_TO_BE2: [
|
||||
(18, 90, 99),
|
||||
(19, 95, 100),
|
||||
(19, 95, 100),
|
||||
(20, 100, 0),
|
||||
(22, 110, 0),
|
||||
(24, 120, 0),
|
||||
(24, 120, 0),
|
||||
(30, 150, 0),
|
||||
(29, 165, 0),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def decide(odds: FateOdds, chaos_level: int = 5) -> FateOutcome:
|
||||
"""Decide the outcome of a situation using the Fate Chart"""
|
||||
|
||||
# Chaos levels range from 1 and 9 (both included), but list indices start at 0
|
||||
chaos_level -= 1
|
||||
|
||||
eyes, yes, eno = TABLE[odds][chaos_level]
|
||||
value = Die("d100").roll()
|
||||
|
||||
if value <= eyes:
|
||||
return FateOutcome.EXC_YES
|
||||
|
||||
if value <= yes:
|
||||
return FateOutcome.YES
|
||||
|
||||
if value >= eno:
|
||||
return FateOutcome.EXC_NO
|
||||
|
||||
return FateOutcome.NO
|
@ -2,3 +2,52 @@
|
||||
# SPDX-FileContributor: Gergely Polonkai
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Oracle classes and related functions"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Type
|
||||
|
||||
import yaml
|
||||
|
||||
from .base import Oracle
|
||||
from .object_generator import ObjectGeneratorOracle
|
||||
from .random_choice import RandomChoiceOracle
|
||||
|
||||
|
||||
def generate_type_classes(class_list: dict[str, Any]) -> dict[str, Type[Oracle]]:
|
||||
"""Generate a dictionary of oracle type handlers"""
|
||||
|
||||
ret: dict[str, Type[Oracle]] = {}
|
||||
|
||||
for klass in class_list.values():
|
||||
if not isinstance(klass, type) or klass == Oracle or not issubclass(klass, Oracle):
|
||||
continue
|
||||
|
||||
if klass.TYPE_MARKER in ret:
|
||||
raise KeyError(
|
||||
f"{ret[klass.TYPE_MARKER].__name__} is already registered as a handler for {klass.TYPE_MARKER}"
|
||||
)
|
||||
|
||||
ret[klass.TYPE_MARKER] = klass
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
TYPE_CLASSES: dict[str, Type[Oracle]] = generate_type_classes(globals())
|
||||
|
||||
|
||||
def load_oracle_from_yaml(file_path: str | Path) -> Oracle:
|
||||
"""Create an Oracle from a YAML file"""
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as fhand:
|
||||
data = yaml.safe_load(fhand)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
raise TypeError("Oracle data must be a YAML object")
|
||||
|
||||
if (generator_type := data.get("type")) not in TYPE_CLASSES:
|
||||
raise KeyError(f"No information on how to handle {generator_type} data")
|
||||
|
||||
handler_class = TYPE_CLASSES[generator_type]
|
||||
|
||||
return handler_class(data)
|
||||
|
16
poetry.lock
generated
16
poetry.lock
generated
@ -873,7 +873,7 @@ version = "6.0.2"
|
||||
description = "YAML parser and emitter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||
@ -994,6 +994,18 @@ files = [
|
||||
{file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.12.20250516"
|
||||
description = "Typing stubs for PyYAML"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530"},
|
||||
{file = "types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.13.2"
|
||||
@ -1042,4 +1054,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "~3.13"
|
||||
content-hash = "c778f03ed4db825597abdfa53d9dc90fed168d978a929383922ab8acabe71f54"
|
||||
content-hash = "4450e2a03c5ea31eee3b6ddbb097737a778911e8ea1d4d389229a39596ffa9d9"
|
||||
|
@ -14,6 +14,7 @@ license = {text = "GPL-3.0-or-later"}
|
||||
readme = "README.md"
|
||||
requires-python = "~3.13"
|
||||
dependencies = [
|
||||
"pyyaml (>=6.0.2,<7.0.0)"
|
||||
]
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
@ -27,6 +28,7 @@ pytest = "^8.3.5"
|
||||
pytest-cov = "^6.1.1"
|
||||
pytest-mock = "^3.14.0"
|
||||
reuse = "^5.0.2"
|
||||
types-pyyaml = "^6.0.12.20250516"
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
@ -7,7 +7,7 @@
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from gm_assistant.dice import Die
|
||||
from gm_assistant.dice import Die, d666
|
||||
|
||||
|
||||
def test_parse_empty() -> None:
|
||||
@ -143,3 +143,45 @@ def test_roll_complex(mocker: MockerFixture) -> None:
|
||||
mocker.call(1, 4),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_d666_strict_order(mocker: MockerFixture) -> None:
|
||||
"""Test ``d666()`` with strict dice order"""
|
||||
|
||||
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=[2, 4, 6])
|
||||
|
||||
assert d666(strict_order=True) == [246]
|
||||
assert mocked_randint.call_count == 3
|
||||
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6), mocker.call(1, 6)])
|
||||
|
||||
|
||||
def test_d666_any_order(mocker: MockerFixture) -> None:
|
||||
"""Test ``d666()`` without strict order"""
|
||||
|
||||
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=[2, 4, 6])
|
||||
|
||||
assert d666(strict_order=False) == [246, 264, 426, 462, 624, 642]
|
||||
assert mocked_randint.call_count == 3
|
||||
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6), mocker.call(1, 6)])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dice_values", [pytest.param((1, 1, 2)), pytest.param((1, 2, 1)), pytest.param((2, 1, 1))])
|
||||
def test_d666_equal_dice(dice_values: tuple[int, int, int], mocker: MockerFixture) -> None:
|
||||
"""Test ``d666()`` when some dice are equal"""
|
||||
|
||||
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", side_effect=dice_values)
|
||||
|
||||
assert set(d666()) == set((112, 121, 211))
|
||||
assert mocked_randint.call_count == 3
|
||||
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6), mocker.call(1, 6)])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("strict_order", (True, False), ids=("strict", "any"))
|
||||
def test_d666_all_equal(strict_order: bool, mocker: MockerFixture) -> None:
|
||||
"""Test ``d666()`` when all dice are equal"""
|
||||
|
||||
mocked_randint = mocker.patch("gm_assistant.dice.random.randint", return_value=5)
|
||||
|
||||
assert d666(strict_order=strict_order) == [555]
|
||||
assert mocked_randint.call_count == 3
|
||||
mocked_randint.assert_has_calls([mocker.call(1, 6), mocker.call(1, 6), mocker.call(1, 6)])
|
||||
|
51
tests/test_fate_chart.py
Normal file
51
tests/test_fate_chart.py
Normal file
@ -0,0 +1,51 @@
|
||||
# SPDX-FileCopyrightText: 2025 2025
|
||||
# SPDX-FileContributor: Gergely Polonkai
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tests for the Fate Chart module"""
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from gm_assistant.fate_chart import FateOdds, FateOutcome, decide
|
||||
|
||||
|
||||
@pytest.mark.parametrize("odds", FateOdds)
|
||||
@pytest.mark.parametrize("chaos_level", range(9))
|
||||
def test_all_valid_values(odds: FateOdds, chaos_level: int) -> None:
|
||||
"""Test if every valid value yields some result"""
|
||||
|
||||
assert isinstance(decide(odds, chaos_level=chaos_level), FateOutcome)
|
||||
|
||||
|
||||
# WARNING
|
||||
#
|
||||
# Tests below this line rely on exact values in the fate_chart module
|
||||
|
||||
|
||||
def test_exceptional_yes(mocker: MockerFixture) -> None:
|
||||
"""Test if exceptional yes result is returned"""
|
||||
|
||||
mocker.patch("gm_assistant.fate_chart.Die.roll", return_value=10)
|
||||
assert decide(FateOdds.FIFTY_FIFTY, chaos_level=5) == FateOutcome.EXC_YES
|
||||
|
||||
|
||||
def test_yes(mocker: MockerFixture) -> None:
|
||||
"""Test if yes result is returned"""
|
||||
|
||||
mocker.patch("gm_assistant.fate_chart.Die.roll", return_value=50)
|
||||
assert decide(FateOdds.FIFTY_FIFTY, chaos_level=5) == FateOutcome.YES
|
||||
|
||||
|
||||
def test_exceptional_no(mocker: MockerFixture) -> None:
|
||||
"""Test if exceptional no result is returned"""
|
||||
|
||||
mocker.patch("gm_assistant.fate_chart.Die.roll", return_value=91)
|
||||
assert decide(FateOdds.FIFTY_FIFTY, chaos_level=5) == FateOutcome.EXC_NO
|
||||
|
||||
|
||||
def test_no(mocker: MockerFixture) -> None:
|
||||
"""Test if exceptional no result is returned"""
|
||||
|
||||
mocker.patch("gm_assistant.fate_chart.Die.roll", return_value=51)
|
||||
assert decide(FateOdds.FIFTY_FIFTY, chaos_level=5) == FateOutcome.NO
|
55
tests/test_load_oracle_from_yaml.py
Normal file
55
tests/test_load_oracle_from_yaml.py
Normal file
@ -0,0 +1,55 @@
|
||||
# SPDX-FileCopyrightText: 2025 2025
|
||||
# SPDX-FileContributor: Gergely Polonkai
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tests for the ``load_oracle_from_yaml`` function"""
|
||||
|
||||
import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from gm_assistant.oracle import load_oracle_from_yaml
|
||||
from gm_assistant.oracle.random_choice import RandomChoiceOracle
|
||||
|
||||
|
||||
def test_non_object(mocker: MockerFixture) -> None:
|
||||
"""Test loading something that is not a YAML object"""
|
||||
|
||||
mocker.patch("gm_assistant.oracle.open")
|
||||
mocker.patch("gm_assistant.oracle.yaml.safe_load", return_value=[])
|
||||
|
||||
with pytest.raises(TypeError) as ctx:
|
||||
load_oracle_from_yaml("test_file")
|
||||
|
||||
assert str(ctx.value) == "Oracle data must be a YAML object"
|
||||
|
||||
|
||||
# WARNING
|
||||
#
|
||||
# Tests below this line rely on specific classes getting loaded into gm_assistant.oracle.TYPE_CLASSES
|
||||
|
||||
|
||||
def test_unknown_type(mocker: MockerFixture) -> None:
|
||||
"""Test loading an oracle with an unknown type"""
|
||||
|
||||
mocker.patch("gm_assistant.oracle.open")
|
||||
mocker.patch("gm_assistant.oracle.yaml.safe_load", return_value={"type": "something-non-existing"})
|
||||
|
||||
with pytest.raises(KeyError) as ctx:
|
||||
load_oracle_from_yaml("test_file")
|
||||
|
||||
assert str(ctx.value) == "'No information on how to handle something-non-existing data'"
|
||||
|
||||
|
||||
def test_load_oracle(mocker: MockerFixture) -> None:
|
||||
"""Test loading a specific oracle"""
|
||||
|
||||
mocker.patch("gm_assistant.oracle.open")
|
||||
mocker.patch(
|
||||
"gm_assistant.oracle.yaml.safe_load",
|
||||
return_value={"type": "random-choice", "name": "Test Oracle", "source": "Test Source", "choices": ["A", "B"]},
|
||||
)
|
||||
|
||||
oracle = load_oracle_from_yaml("test_file")
|
||||
|
||||
assert isinstance(oracle, RandomChoiceOracle)
|
||||
assert oracle.choices == ["A", "B"]
|
53
tests/test_oracle_type_class_lister.py
Normal file
53
tests/test_oracle_type_class_lister.py
Normal file
@ -0,0 +1,53 @@
|
||||
# SPDX-FileCopyrightText: 2025 2025
|
||||
# SPDX-FileContributor: Gergely Polonkai
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tests for the type class lister"""
|
||||
|
||||
import pytest
|
||||
|
||||
from gm_assistant.oracle import generate_type_classes
|
||||
from gm_assistant.oracle.base import Oracle
|
||||
from gm_assistant.oracle.object_generator import ObjectGeneratorOracle
|
||||
|
||||
|
||||
class _TestOracle(Oracle):
|
||||
"""Test oracle class that has the same marker as ObjectGeneratorOracle"""
|
||||
|
||||
TYPE_MARKER = ObjectGeneratorOracle.TYPE_MARKER
|
||||
|
||||
def generate(self) -> str: # pragma: no cover
|
||||
return ""
|
||||
|
||||
|
||||
def test_generate_empty() -> None:
|
||||
"""Test generating the type class list from an empty dictionary"""
|
||||
|
||||
assert generate_type_classes({}) == {} # pylint: disable=use-implicit-booleaness-not-comparison
|
||||
|
||||
|
||||
def test_nontype_not_present() -> None:
|
||||
"""Test that non-types don’t get included in the results"""
|
||||
|
||||
assert generate_type_classes({"test": True}) == {} # pylint: disable=use-implicit-booleaness-not-comparison
|
||||
|
||||
|
||||
def test_non_oracle_not_present() -> None:
|
||||
"""Test that non-oracle types don’t get included in the results"""
|
||||
|
||||
assert generate_type_classes({"test": dict}) == {} # pylint: disable=use-implicit-booleaness-not-comparison
|
||||
|
||||
|
||||
def test_oracle_not_present() -> None:
|
||||
"""Test that the ``Oracle`` class doesn’t get included in the results"""
|
||||
|
||||
assert generate_type_classes({"oracle": Oracle}) == {} # pylint: disable=use-implicit-booleaness-not-comparison
|
||||
|
||||
|
||||
def test_duplace_type_marker() -> None:
|
||||
"""Test if ``generate_type_classes`` raises an error if a type marker appears twice"""
|
||||
|
||||
with pytest.raises(KeyError) as ctx:
|
||||
generate_type_classes({"ObjectGeneratorOracle": ObjectGeneratorOracle, "TestOracle": _TestOracle})
|
||||
|
||||
assert str(ctx.value) == "'ObjectGeneratorOracle is already registered as a handler for object-generator'"
|
Loading…
x
Reference in New Issue
Block a user