Skip to content

Layer Coercion

app.application.services.helpers.layer_coercion

View source code

Enum coercion workers for Qt QVariant round-trips.

The VideoDataLayer and ImportMode enums are both str-based.

See Also

Why coercion is necessary

str-based enums inherit from str. When such an object is stored as userData in a PySide6 QComboBox via addItem(label, userData) and later retrieved via currentData(), Qt's internal QVariant machinery sees a str subclass, strips the Python subclass identity, and returns a bare str instead of the enum member.

This is not a signal/slot crossing issue — it happens in a synchronous method call:

addItem("Layer A", VideoDataLayer.A)   # VideoDataLayer.A stored in QVariant
currentData()                           # returns "a" (plain str)

Confirmed call chain (observed 2026-07-29):

ImportExportDialog._init_widgets()
    self._layer_combo.addItem(_LAYER_LABELS[layer], layer)   # enum → QVariant
                                                              #        ↓ coerced
ImportExportDialog._build_config()
    layer = self._layer_combo.currentData()   # returns "a" (str), not VideoDataLayer.A
    ImportExportConfig(layer="a", ...)        # str stored in dataclass

ImportExportHandler._do_export()
    cfg.layer.name                            # AttributeError: 'str' has no .name

DetectionExportService.export_layer(layer="a")
    layer.value                               # AttributeError (also caught in log):
    # WARNING | DetectionExportService was provided with string literal 'a'

Fix locations

PRIMARY — ImportExportDialog._build_config() calls ensure_layer_enum() immediately after currentData() so the coerced str is re-wrapped before it ever leaves the dialog.

SECONDARY — All four import/export services (DetectionImportService, DetectionExportService, TrackingImportService, TrackingExportService) call ensure_layer_enum() at their entry points as a safety net for any future programmatic callers that bypass the dialog.

TERTIARY — Application.import_layer() and Application.export_layer() also coerce at the façade boundary.

Observed similarly for ImportMode:

mode = self._mode_combo.currentData()   # returns "merge_replace" (str)
mode.display_label                       # AttributeError: 'str' has no attribute 'display_label'

Do NOT store str-based enum members as QComboBox userData without coercing them back to enums after currentData() retrieval. This pattern silently breaks code that relies on enum attributes/properties (name, value, display_label).

Layer: Application. Context: Coordinates domain state and infrastructure to complete application use cases.

ensure_layer_enum(layer: VideoDataLayer | str) -> VideoDataLayer

Safely coerce a layer to a VideoDataLayer enum.

PARAMETER DESCRIPTION
layer

Either a VideoDataLayer enum or a string (e.g., "a" or "A").

TYPE: VideoDataLayer | str

RETURNS DESCRIPTION
VideoDataLayer

The corresponding enum member.

TYPE: VideoDataLayer

RAISES DESCRIPTION
ValueError

If the input is neither a valid enum nor a recognised string.

Note

Accepts both lowercase ("a") and uppercase ("A") strings. This handles the common case where Qt or serialisation coerces enums to strings.

ensure_import_mode(mode: ImportMode | str) -> ImportMode

Safely coerce an import mode to an ImportMode enum.

PARAMETER DESCRIPTION
mode

Either an ImportMode enum or a string (enum value/name).

TYPE: ImportMode | str

RETURNS DESCRIPTION
ImportMode

The corresponding enum member.

TYPE: ImportMode

RAISES DESCRIPTION
ValueError

If the input is neither a valid enum nor a recognised string.

Note

Mirrors ensure_layer_enum for the same Qt QVariant round-trip issue affecting str-based enums passed through QComboBox.currentData().