Skip to content

Conversation

@SilviaAmAm
Copy link
Collaborator

@SilviaAmAm SilviaAmAm marked this pull request as draft November 27, 2025 16:54
@codecov-commenter
Copy link

codecov-commenter commented Nov 28, 2025

Codecov Report

❌ Patch coverage is 83.87097% with 40 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.92%. Comparing base (380ffdd) to head (03a04ba).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
...r/external_registers/setup_configuration/models.py 0.00% 13 Missing ⚠️
...er/external_registers/setup_configuration/steps.py 0.00% 13 Missing ⚠️
...src/openarchiefbeheer/external_registers/plugin.py 86.36% 6 Missing ⚠️
...c/openarchiefbeheer/external_registers/registry.py 82.60% 4 Missing ⚠️
...eer/external_registers/contrib/openklant/plugin.py 85.00% 3 Missing ⚠️
...ngs/pages/health-check/HealthCheckSettingsPage.tsx 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #921      +/-   ##
==========================================
- Coverage   90.28%   89.92%   -0.36%     
==========================================
  Files         214      223       +9     
  Lines        6885     7068     +183     
  Branches      696      703       +7     
==========================================
+ Hits         6216     6356     +140     
- Misses        669      712      +43     
Flag Coverage Δ
backend 90.89% <83.11%> (-0.57%) ⬇️
jest 34.10% <11.76%> (-0.20%) ⬇️
storybook 83.46% <94.11%> (-0.06%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@SilviaAmAm SilviaAmAm marked this pull request as ready for review December 1, 2025 10:53
@SilviaAmAm SilviaAmAm requested a review from CharString December 1, 2025 10:53
<li>{{ error.message }}</li>
{% endfor %}
</ul>
<p>{% translate 'The configuration is complete.' %}</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A checklist with succeeded checks ✔️ shows that checks were performed.
If misconfigured, or configuration drift occurred (e.g. updates), and somehow no checks are collected or only a subset, then all checks pass, but health may not be okay.
Perhaps that's all impossible here, but the user doesn't know that.

Maybe we can have a default in the library that uses `<details>` and `<summary>` tags to
  • not overwhelming with data
  • still provide access to that data to help end-user, application manager, dev ops and 1st line support spot issues.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the template tag to the library. I think it still needs some work, but it is functional for now.

)


register = Registry()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do need to bind the PluginT type var, or it will be a Registry[Unknown]

Suggested change
register = Registry()
register: Registry[AbstractBasePlugin] = Registry()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS, to prevent the Unknown/Any judgement, you can set a default type if none is annotated:

class Registry[PluginT: AbstractBasePlugin = AbstractBasePlugin]:

The syntax is GenericType[TypeVarName: UpperBoundType = DefaultType]. where GenericType can be a function, class or method.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes! I did this in the end: Registry[AbstractBasePlugin[object]] because AbstractBasePlugin also has a generic type 😵‍💫

Copy link
Contributor

@CharString CharString Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm. If we remove the bound from Registry, you can reuse it for the health checks, and have a Registry[HealthCheckPlugin] of which the __iter__ method is a valid checks_collector.... Oh wait. The __call__ does more than just adding to the collection.

Comment on lines +15 to +26
def __call__(self, identifier: str) -> Callable[[type[PluginT]], type[PluginT]]:
def decorator(plugin_cls: type[PluginT]) -> type[PluginT]:
if identifier in self._registry:
raise ValueError(
f"The unique identifier '{identifier}' is already present "
"in the registry."
)

self._registry[identifier] = plugin_cls(identifier=identifier)
return plugin_cls

return decorator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to express that the decorator doesn't just return some type[PluginT] but the same that was passed in.

Suggested change
def __call__(self, identifier: str) -> Callable[[type[PluginT]], type[PluginT]]:
def decorator(plugin_cls: type[PluginT]) -> type[PluginT]:
if identifier in self._registry:
raise ValueError(
f"The unique identifier '{identifier}' is already present "
"in the registry."
)
self._registry[identifier] = plugin_cls(identifier=identifier)
return plugin_cls
return decorator
def __call__[T: type[PluginT]](self, identifier: str) -> Callable[[T], T]:
def decorator(plugin_cls: T) -> T:
if identifier in self._registry:
raise ValueError(
f"The unique identifier '{identifier}' is already present "
"in the registry."
)
self._registry[identifier] = plugin_cls(identifier=identifier)
return plugin_cls
return decorator

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that TypeVar constraint type cannot be generic (since pluginT is generic). I can't get it to be happy with [T: type[AbstractBasePlugin]] instead 😩

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed. We can drop the generic from Registry, and replace all occurrences of PluginT with AbstractBasePlugin. (or define it as a an alias type PluginT = AbastractBasePlugin for shorthand... or rename it to BasePlugin, as it is clear that it's an abstract from its inheritance from ABC. But that's a matter of taste.)

@SilviaAmAm SilviaAmAm requested a review from CharString December 4, 2025 14:04
from openarchiefbeheer.external_registers.plugin import AbstractBasePlugin


class Registry[PluginT: AbstractBasePlugin]:
Copy link
Collaborator Author

@SilviaAmAm SilviaAmAm Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed with Chris:
The [PluginT: AbstractBasePlugin] can be moved to the call function.

And instead of being generic it can be only AbstractBasePlugin in the other places

Copy link
Contributor

@CharString CharString left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there. 🙂 👍

Comment on lines +15 to +26
def __call__(self, identifier: str) -> Callable[[type[PluginT]], type[PluginT]]:
def decorator(plugin_cls: type[PluginT]) -> type[PluginT]:
if identifier in self._registry:
raise ValueError(
f"The unique identifier '{identifier}' is already present "
"in the registry."
)

self._registry[identifier] = plugin_cls(identifier=identifier)
return plugin_cls

return decorator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed. We can drop the generic from Registry, and replace all occurrences of PluginT with AbstractBasePlugin. (or define it as a an alias type PluginT = AbastractBasePlugin for shorthand... or rename it to BasePlugin, as it is clear that it's an abstract from its inheritance from ABC. But that's a matter of taste.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plugin system for external registers with related objects

4 participants