Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .importlinter
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[importlinter]
root_package = anonymizer

# The NDD adapter layer is a leaf boundary: it must never import the engine
# sub-workflows that call it.
[importlinter:contract:ndd-adapter-is-a-leaf]
name = engine.ndd must not import engine sub-workflows
type = forbidden
source_modules =
anonymizer.engine.ndd
forbidden_modules =
anonymizer.engine.detection
anonymizer.engine.replace
anonymizer.engine.rewrite
anonymizer.engine.evaluation
anonymizer.interface.anonymizer

# User-facing config must stay import-free of the engine and interface.
[importlinter:contract:config-is-lowest-layer]
name = config imports neither engine nor interface
type = forbidden
source_modules =
anonymizer.config
forbidden_modules =
anonymizer.engine
anonymizer.interface

# Display package layering (active after Group A): the layout module may use
# the coercion/span modules, never the reverse.
[importlinter:contract:display-layering]
name = display internals do not import the layout module
type = forbidden
source_modules =
anonymizer.interface.display.payload_coercion
anonymizer.interface.display.replaced_spans
forbidden_modules =
anonymizer.interface.display.record_html

# Facade extractions (active after Group B2) must not re-import the facade.
[importlinter:contract:facade-extractions-no-backedge]
name = output_columns / run_telemetry do not import the Anonymizer facade
type = forbidden
source_modules =
anonymizer.interface.output_columns
anonymizer.interface.run_telemetry
forbidden_modules =
anonymizer.interface.anonymizer
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ anonymizer-chunked-validation = "anonymizer.engine.workflow_columns.detection.pl

[dependency-groups]
dev = [
"import-linter>=2.13",
"pre-commit>=4.0.0,<5",
"pytest>=9.0.3,<10",
"pytest-cov>=7.0,<8",
Expand Down
6 changes: 3 additions & 3 deletions src/anonymizer/engine/io/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ def read_input(input_data: AnonymizerInput, *, nrows: int | None = None) -> Reso


# Suffixes appended to the user's text column to form per-mode output columns
# (see ``_rename_output_columns`` in ``anonymizer.interface.anonymizer``).
# (see ``rename_output_columns`` in ``anonymizer.interface.output_columns``).
_OUTPUT_COLUMN_SUFFIXES: tuple[str, ...] = ("_replaced", "_with_spans", "_rewritten")

# Fixed user-facing output column names that don't depend on the text column
# (see ``_build_user_dataframe`` in ``anonymizer.interface.anonymizer``).
# (see ``build_user_dataframe`` in ``anonymizer.interface.output_columns``).
_STATIC_OUTPUT_COLUMNS: tuple[str, ...] = (
COL_FINAL_ENTITIES,
COL_UTILITY_SCORE,
Expand Down Expand Up @@ -88,7 +88,7 @@ def _resolve_output_column_collisions(dataframe: pd.DataFrame, *, selected_text_
``final_entities__input_replaced``) are re-checked against the remaining
user columns. That second pass is necessary — without it, an input column
that matches a *post-rename* derived name (``final_entities__input_replaced``)
would never be reserved, and ``_rename_output_columns`` would later
would never be reserved, and ``rename_output_columns`` would later
overwrite it, producing duplicate user-facing labels.

The returned ``ResolvedInput.resolved_text_column`` reflects the final
Expand Down
Loading
Loading