fix(core): consume the fallback flag at the call site, not in the helper

Review finding, reproduced. `call_custom_method(..., **kwargs)` builds a
fresh dict from the unpacking, so popping `fallback_on_custom_error`
inside the helper left the caller's own kwargs untouched. On the fallback
path the flag was then forwarded straight into the default
implementation, which is exactly the case the flag exists for.

Instrumenting the default exporter shows it arriving:

    config handed to the default exporter: {'fallback_on_custom_error': True}

Most defaults take **kwargs and ignore it, which is why nothing failed
loudly, but any default with a fixed signature raises TypeError on it.
The helper's docstring promised the flag was never forwarded, so the
promise was false rather than merely untidy.

All 58 sites now pop the flag from their own bag and pass it explicitly.
One site in normalize/methods.py names its bag `**context` rather than
`**kwargs`, and is handled too.

3 further tests: the flag reaches neither the default implementation nor
a successful custom method, and a per-module guard that every call site
has a matching pop, since a site that forgets one reintroduces the leak
silently.

Failure set across the six affected modules is unchanged against
upstream/main: 37 pre-existing, none new.
This commit is contained in:
FABIOTESS
2026-08-19 17:41:08 +01:00
parent d5dc4eabac
commit 71cffb15e9
7 changed files with 267 additions and 58 deletions
+14 -4
View File
@@ -120,8 +120,9 @@ def generate_embeddings(
# Check for custom method in registry
custom_method = method_registry.get("generation", method)
if custom_method:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, data_type=data_type, **kwargs
logger, method, custom_method, data, data_type=data_type, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -167,7 +168,10 @@ def embed_text(
# Check for custom method in registry
custom_method = method_registry.get("text", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, text, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, text, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -224,7 +228,10 @@ def calculate_similarity(
# Check for custom method in registry
custom_method = method_registry.get("similarity", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, embedding1, embedding2, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, embedding1, embedding2, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -268,7 +275,10 @@ def pool_embeddings(
# Check for custom method in registry
custom_method = method_registry.get("pooling", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, embeddings, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, embeddings, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
+32 -13
View File
@@ -222,8 +222,9 @@ def export_rdf(
# Check for custom method in registry
custom_method = method_registry.get("rdf", method)
if custom_method and custom_method is not export_rdf:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, file_path, format=format, **kwargs
logger, method, custom_method, data, file_path, format=format, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -270,8 +271,9 @@ def export_json(
# Check for custom method in registry
custom_method = method_registry.get("json", method)
if custom_method and custom_method is not export_json:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, file_path, format=format, **kwargs
logger, method, custom_method, data, file_path, format=format, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -315,7 +317,10 @@ def export_csv(
# Check for custom method in registry
custom_method = method_registry.get("csv", method)
if custom_method and custom_method is not export_csv:
result = call_custom_method(logger, method, custom_method, data, file_path, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -357,7 +362,10 @@ def export_arrow(
# Check for custom method in registry
custom_method = method_registry.get("arrow", method)
if custom_method and custom_method is not export_arrow:
result = call_custom_method(logger, method, custom_method, data, file_path, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -414,8 +422,9 @@ def export_parquet(
# Check for custom method in registry
custom_method = method_registry.get("parquet", method)
if custom_method and custom_method is not export_parquet:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, file_path, compression=compression, **kwargs
logger, method, custom_method, data, file_path, compression=compression, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -464,8 +473,9 @@ def export_graph(
# Check for custom method in registry
custom_method = method_registry.get("graph", method)
if custom_method and custom_method is not export_graph:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, graph_data, file_path, format=format, **kwargs
logger, method, custom_method, graph_data, file_path, format=format, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -529,7 +539,10 @@ def export_yaml(
# Check for custom method in registry
custom_method = method_registry.get("yaml", method)
if custom_method and custom_method is not export_yaml:
result = call_custom_method(logger, method, custom_method, data, file_path, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -583,8 +596,9 @@ def export_owl(
# Check for custom method in registry
custom_method = method_registry.get("owl", method)
if custom_method and custom_method is not export_owl:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, ontology, file_path, format=format, **kwargs
logger, method, custom_method, ontology, file_path, format=format, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -634,8 +648,9 @@ def export_vector(
# Check for custom method in registry
custom_method = method_registry.get("vector", method)
if custom_method and custom_method is not export_vector:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, vectors, file_path, format=format, **kwargs
logger, method, custom_method, vectors, file_path, format=format, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -681,8 +696,9 @@ def export_lpg(
# Check for custom method in registry
custom_method = method_registry.get("lpg", method)
if custom_method and custom_method is not export_lpg:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, knowledge_graph, file_path, **kwargs
logger, method, custom_method, knowledge_graph, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -723,8 +739,9 @@ def export_neo4j_csv(
"""
custom_method = method_registry.get("neo4j_csv", method)
if custom_method and custom_method is not export_neo4j_csv:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, knowledge_graph, output_dir, **kwargs
logger, method, custom_method, knowledge_graph, output_dir, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -792,8 +809,9 @@ def export_arango(
# Check for custom method in registry
custom_method = method_registry.get("arango", method)
if custom_method and custom_method is not export_arango:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, knowledge_graph, file_path, **kwargs
logger, method, custom_method, knowledge_graph, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -842,8 +860,9 @@ def generate_report(
# Check for custom method in registry
custom_method = method_registry.get("report", method)
if custom_method and custom_method is not generate_report:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, file_path, format=format, **kwargs
logger, method, custom_method, data, file_path, format=format, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
+52 -13
View File
@@ -249,7 +249,10 @@ def ingest_file(
# Check for custom method in registry
custom_method = method_registry.get("file", method)
if custom_method and custom_method != ingest_file:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -312,7 +315,10 @@ def ingest_parquet(
"""
custom_method = method_registry.get("parquet", method)
if custom_method and custom_method != ingest_parquet:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -388,7 +394,10 @@ def ingest_arrow(
"""
custom_method = method_registry.get("arrow", method)
if custom_method and custom_method != ingest_arrow:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -469,7 +478,10 @@ def ingest_xml(
"""
custom_method = method_registry.get("xml", method)
if custom_method and custom_method != ingest_xml:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -530,7 +542,10 @@ def ingest_web(
# Check for custom method in registry
custom_method = method_registry.get("web", method)
if custom_method and custom_method != ingest_web:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -617,7 +632,10 @@ def ingest_public_api(
"""
custom_method = method_registry.get("public_api", method)
if custom_method and custom_method != ingest_public_api:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -701,7 +719,10 @@ def ingest_feed(
# Check for custom method in registry
custom_method = method_registry.get("feed", method)
if custom_method and custom_method != ingest_feed:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -767,7 +788,10 @@ def ingest_stream(
# Check for custom method in registry
custom_method = method_registry.get("stream", method)
if custom_method and custom_method != ingest_stream:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -841,7 +865,10 @@ def ingest_repository(
# Check for custom method in registry
custom_method = method_registry.get("repo", method)
if custom_method and custom_method != ingest_repository:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -910,7 +937,10 @@ def ingest_email(
# Check for custom method in registry
custom_method = method_registry.get("email", method)
if custom_method and custom_method != ingest_email:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -986,7 +1016,10 @@ def ingest_ontology(
# Check for custom method in registry
custom_method = method_registry.get("ontology", method)
if custom_method and custom_method != ingest_ontology:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -1049,7 +1082,10 @@ def ingest_database(
if method:
custom_method = method_registry.get("db", method)
if custom_method and custom_method != ingest_database:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -1153,7 +1189,10 @@ def ingest_mcp(
# Check for custom method in registry
custom_method = method_registry.get("mcp", method)
if custom_method and custom_method != ingest_mcp:
result = call_custom_method(logger, method, custom_method, source, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, source, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
+12 -3
View File
@@ -190,7 +190,10 @@ def build_kg(
# Check for custom method in registry
custom_method = method_registry.get("build", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, sources, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, sources, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -235,7 +238,10 @@ def analyze_graph(
# Check for custom method in registry
custom_method = method_registry.get("analyze", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, graph, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, graph, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -436,7 +442,10 @@ def analyze_connectivity(
# Check for custom method in registry
custom_method = method_registry.get("connectivity", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, graph, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, graph, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
+42 -13
View File
@@ -169,7 +169,10 @@ def normalize_text(text: str, method: str = "default", **kwargs) -> str:
"""
custom_method = method_registry.get("text", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, text, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, text, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -210,7 +213,10 @@ def clean_text(text: str, method: str = "default", **kwargs) -> str:
"""
custom_method = method_registry.get("clean", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, text, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, text, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -257,8 +263,9 @@ def normalize_entity(
"""
custom_method = method_registry.get("entity", method)
if custom_method:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, entity_name, entity_type, **kwargs
logger, method, custom_method, entity_name, entity_type, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -303,8 +310,9 @@ def resolve_aliases(
"""
custom_method = method_registry.get("entity", method)
if custom_method:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, entity_name, entity_type, **kwargs
logger, method, custom_method, entity_name, entity_type, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -349,7 +357,10 @@ def disambiguate_entity(
"""
custom_method = method_registry.get("entity", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, entity_name, **context)
fallback = context.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, entity_name, fallback_on_custom_error=fallback, **context
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -401,8 +412,9 @@ def normalize_date(
"""
custom_method = method_registry.get("date", method)
if custom_method:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, date_input, format, timezone, **kwargs
logger, method, custom_method, date_input, format, timezone, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -441,7 +453,10 @@ def normalize_time(time_input: Any, method: str = "default", **kwargs) -> str:
"""
custom_method = method_registry.get("date", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, time_input, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, time_input, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -484,7 +499,10 @@ def normalize_number(
"""
custom_method = method_registry.get("number", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, number_input, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, number_input, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -525,7 +543,10 @@ def normalize_quantity(
"""
custom_method = method_registry.get("number", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, quantity_input, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, quantity_input, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -578,8 +599,9 @@ def clean_data(
"""
custom_method = method_registry.get("clean", method)
if custom_method:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, dataset, remove_duplicates, validate, handle_missing, **kwargs
logger, method, custom_method, dataset, remove_duplicates, validate, handle_missing, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -630,8 +652,9 @@ def detect_duplicates(
"""
custom_method = method_registry.get("clean", method)
if custom_method:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, dataset, threshold, key_fields, **kwargs
logger, method, custom_method, dataset, threshold, key_fields, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -676,7 +699,10 @@ def detect_language(
"""
custom_method = method_registry.get("language", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, text, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, text, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -734,7 +760,10 @@ def handle_encoding(
"""
custom_method = method_registry.get("encoding", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, data, operation, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, operation, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
+46 -12
View File
@@ -179,7 +179,10 @@ def parse_document(
"""
custom_method = method_registry.get("document", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, file_type, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, file_type, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -287,8 +290,9 @@ def parse_web_content(
"""
custom_method = method_registry.get("web", method)
if custom_method:
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, content, content_type, base_url, **kwargs
logger, method, custom_method, content, content_type, base_url, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -342,7 +346,10 @@ def parse_structured_data(
"""
custom_method = method_registry.get("structured", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, data, data_format, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, data, data_format, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -386,7 +393,10 @@ def parse_email(
"""
custom_method = method_registry.get("email", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, email_content, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, email_content, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -433,7 +443,10 @@ def parse_code(
"""
custom_method = method_registry.get("code", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, language, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, language, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -483,7 +496,10 @@ def parse_media(
"""
custom_method = method_registry.get("media", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, media_type, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, media_type, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -526,7 +542,10 @@ def parse_pdf(
"""
custom_method = method_registry.get("document", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -567,7 +586,10 @@ def parse_docx(
"""
custom_method = method_registry.get("document", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -607,7 +629,10 @@ def parse_json(file_path: Union[str, Path], method: str = "default", **kwargs) -
"""
custom_method = method_registry.get("structured", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -651,7 +676,10 @@ def parse_csv(
"""
custom_method = method_registry.get("structured", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, delimiter, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, delimiter, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -687,7 +715,10 @@ def parse_xml(file_path: Union[str, Path], method: str = "default", **kwargs) ->
"""
custom_method = method_registry.get("structured", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -732,7 +763,10 @@ def parse_image(
"""
custom_method = method_registry.get("media", method)
if custom_method:
result = call_custom_method(logger, method, custom_method, file_path, **kwargs)
fallback = kwargs.pop("fallback_on_custom_error", False)
result = call_custom_method(
logger, method, custom_method, file_path, fallback_on_custom_error=fallback, **kwargs
)
if result is not CUSTOM_METHOD_FELL_BACK:
return result
@@ -154,3 +154,72 @@ def test_no_module_still_swallows_custom_method_failures(module_name):
assert "falling back to default" not in source, (
f"semantica/{module_name}/methods.py still swallows custom method failures"
)
# ── Review findings on the first revision of this fix ────────────────────────
def test_the_reserved_flag_never_reaches_the_default_implementation(monkeypatch, tmp_path):
"""
`**kwargs` unpacking builds a fresh dict inside the helper, so popping there
left the caller's own kwargs untouched and the flag was forwarded on to the
default path. The helper documents the flag as never forwarded, so that
promise was false for exactly the case the flag exists for.
"""
seen = {}
class Spy:
def __init__(self, **config):
seen.update(config)
def export(self, *args, **kwargs):
(tmp_path / "written").write_text("default ran")
monkeypatch.setattr(export_methods, "RDFExporter", Spy)
def gate(data, file_path, **kwargs):
raise Refused("rejected")
method_registry.register("rdf", "gate", gate)
export_methods.export_rdf(
KG, str(tmp_path / "out.ttl"), method="gate", fallback_on_custom_error=True
)
assert (tmp_path / "written").exists(), "the default path did not run"
assert "fallback_on_custom_error" not in seen, (
f"the reserved flag was forwarded to the default implementation: {seen}"
)
def test_the_reserved_flag_never_reaches_a_successful_custom_method(tmp_path):
seen = {}
def writer(data, file_path, **kwargs):
seen.update(kwargs)
return "ok"
method_registry.register("rdf", "writer", writer)
result = export_methods.export_rdf(
KG, str(tmp_path / "out.ttl"), method="writer", fallback_on_custom_error=True
)
assert result == "ok"
assert "fallback_on_custom_error" not in seen, seen
@pytest.mark.parametrize(
"module_name", ["export", "ingest", "parse", "normalize", "embeddings", "kg"],
)
def test_every_site_consumes_the_flag_before_forwarding(module_name):
"""A site that forgets the pop reintroduces the leak silently."""
import semantica
source = (
Path(semantica.__file__).parent / module_name / "methods.py"
).read_text(encoding="utf-8")
calls = source.count("result = call_custom_method(")
pops = source.count('.pop("fallback_on_custom_error", False)')
assert calls == pops, (
f"semantica/{module_name}/methods.py has {calls} call site(s) but "
f"{pops} consume the reserved flag"
)