From fe1af87934ea305fd610e43d6e0b0d340dee24ba Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 12 Oct 2026 00:34:53 -0400 Subject: [PATCH 1/2] WIP: one factory and one formatter per type --- src/formbridge/forms/parameter_info_types.py | 6 ++++++ src/formbridge/protocols/preview_formatter.py | 6 ++++++ tests/test_preview_formatter_registry.py | 15 +++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/formbridge/forms/parameter_info_types.py b/src/formbridge/forms/parameter_info_types.py index c1e713f..6ac6081 100644 --- a/src/formbridge/forms/parameter_info_types.py +++ b/src/formbridge/forms/parameter_info_types.py @@ -71,6 +71,12 @@ def register( "Inline dataclass widgets can only be registered for dataclass types; " f"got {dataclass_type!r}." ) + registered = cls._factories.get(dataclass_type) + if registered is not None and registered is not widget_factory: + raise ValueError( + f"{dataclass_type.__qualname__} already has an inline widget factory " + f"({registered!r}); it cannot also use {widget_factory!r}." + ) cls._factories[dataclass_type] = widget_factory @classmethod diff --git a/src/formbridge/protocols/preview_formatter.py b/src/formbridge/protocols/preview_formatter.py index ddf4475..fbcdab1 100644 --- a/src/formbridge/protocols/preview_formatter.py +++ b/src/formbridge/protocols/preview_formatter.py @@ -39,6 +39,12 @@ def register( config_type: Config class to format formatter: Formatter function taking (config, field_name) -> str """ + registered = cls._formatters.get(config_type) + if registered is not None and registered is not formatter: + raise ValueError( + f"{config_type.__qualname__} already has a preview formatter " + f"({registered!r}); it cannot also use {formatter!r}." + ) cls._formatters[config_type] = formatter @classmethod diff --git a/tests/test_preview_formatter_registry.py b/tests/test_preview_formatter_registry.py index 66f0afe..007adb5 100644 --- a/tests/test_preview_formatter_registry.py +++ b/tests/test_preview_formatter_registry.py @@ -55,3 +55,18 @@ class BaseConfig: ) assert PreviewFormatterRegistry.format_field(lazy_type(), "value") == "base" + + +def test_preview_formatter_registration_rejects_a_second_formatter_for_one_type() -> None: + class OnceConfig: + pass + + def formatter(_config: object, field_name: str) -> str: + return f"first:{field_name}" + + PreviewFormatterRegistry.register(OnceConfig, formatter) + PreviewFormatterRegistry.register(OnceConfig, formatter) + + with pytest.raises(ValueError, match="OnceConfig already has a preview formatter"): + PreviewFormatterRegistry.register(OnceConfig, lambda _config, _field_name: "second") + assert PreviewFormatterRegistry.format_field(OnceConfig(), "value") == "first:value" From f901e0fcc73038420c0cf386386f9a92b8155151 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 12 Oct 2026 08:39:15 -0400 Subject: [PATCH 2/2] 0.1.3: one inline-widget factory and one preview formatter per type; require metaclass-registry 0.4 Raises lower bounds to objectstate 3.0.1 and zmqruntime 0.6.4. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 13 +++++++++++++ pyproject.toml | 6 +++--- requirements-ci.txt | 2 +- src/formbridge/__init__.py | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bee7eff..58991b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.1.3 + +Requires `metaclass-registry>=0.4.0`, `objectstate>=3.0.1,<4` and +`zmqruntime>=0.6.4,<0.7`. + +Changed: + +- A type has one inline-widget factory and one preview formatter. + `PreviewFormatterRegistry.register` and the inline-widget factory + registration raise `ValueError` when a different registrant is given for a + type that already has one, instead of replacing it. Registering the same + one again does nothing. + ## 0.1.2 Requires ObjectState 3 (`objectstate>=3.0,<4`). diff --git a/pyproject.toml b/pyproject.toml index 73b59e4..1bb1af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,12 +25,12 @@ keywords = ["forms", "reactive", "dataclass", "ui-bridge", "headless"] dependencies = [ "docutils>=0.20", - "metaclass-registry>=0.1.5", - "objectstate>=3.0,<4", + "metaclass-registry>=0.4.0", + "objectstate>=3.0.1,<4", "psutil>=5.9", "python-introspect>=0.2.7,<0.3", "pyzmq>=22.0", - "zmqruntime>=0.6,<0.7", + "zmqruntime>=0.6.4,<0.7", ] [project.optional-dependencies] diff --git a/requirements-ci.txt b/requirements-ci.txt index 14a296b..4dcdd05 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -1,2 +1,2 @@ # Source candidates needed to validate this checkout before coordinated publication. -# Empty: every first-party dependency is released (objectstate 3.0.0). +# Empty: every first-party dependency is released (objectstate 3.0.1, zmqruntime 0.6.4, metaclass-registry 0.4.0). diff --git a/src/formbridge/__init__.py b/src/formbridge/__init__.py index 088e276..456e797 100644 --- a/src/formbridge/__init__.py +++ b/src/formbridge/__init__.py @@ -4,4 +4,4 @@ nothing and loads no GUI toolkit. """ -__version__ = "0.1.2" +__version__ = "0.1.3"