Skip to content
Open
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
21 changes: 21 additions & 0 deletions e2e-tests/tests/test_api_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,27 @@ def test_given_a_incorrect_analyze_language_input_then_return_error():
assert equal_json_strings(expected_response, response_content)


@pytest.mark.api
def test_given_partially_unsupported_entities_then_return_supported_results():
request_body = """
{
"text": "codice fiscale RSSMRA85M01H501Q, IBAN IT60X0542811101000000123456",
"language": "en",
"entities": ["IT_FISCAL_CODE", "IBAN_CODE"]
}
"""

response_status, response_content = analyze(request_body)

expected_response = """
[
{"entity_type": "IBAN_CODE", "start": 38, "end": 65, "score": 1.0, "analysis_explanation": null}
]
"""
assert response_status == 200
assert equal_json_strings(expected_response, response_content)


@pytest.mark.api
def test_given_a_correlationid_analyze_input_then_return_normal_response():
request_body = """
Expand Down
7 changes: 7 additions & 0 deletions presidio-analyzer/presidio_analyzer/analyzer_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ def analyze(
:param regex_flags: regex flags to be used for when allow_list_match is "regex"
:param nlp_artifacts: precomputed NlpArtifacts
:return: an array of the found entities in the text
:raises ValueError: if no recognizers match the requested entities and language.

Unsupported entities are ignored with a logged warning, while supported
entities are analyzed. Ignoring unsupported entities is deprecated and will
raise an error in a future version. Call
`AnalyzerEngine.get_supported_entities(language)` to find supported entities,
or provide matching ad-hoc recognizers.

:Example:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ def get_recognizers(
as part of the request
:return: A list of the recognizers which supports the supplied entities
and language
:raises ValueError: if language is None, entities is None when all_fields
is False, or no recognizers match the request.

Unsupported entities are ignored with a logged warning. This behavior
is deprecated and will raise an error in a future version. Use
AnalyzerEngine.get_supported_entities(language) to find supported entities,
or provide matching ad-hoc recognizers.
"""
if language is None:
raise ValueError("No language provided")
Expand Down Expand Up @@ -232,7 +239,10 @@ def get_recognizers(
if not subset:
logger.warning(
"Entity %s doesn't have the corresponding"
" recognizer in language : %s",
" recognizer in language : %s. Ignoring unsupported entities"
" is deprecated and will raise an error in a future version."
" Use AnalyzerEngine.get_supported_entities(language) to find"
" supported entities, or add a matching recognizer.",
entity,
language,
)
Expand Down
37 changes: 37 additions & 0 deletions presidio-analyzer/tests/test_analyzer_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
)
from presidio_analyzer.nlp_engine import (
NlpArtifacts,
NoOpNlpEngine,
SpacyNlpEngine,
)
from presidio_analyzer.predefined_recognizers import CreditCardRecognizer
from presidio_analyzer.recognizer_registry import RecognizerRegistryProvider

# noqa: F401
Expand Down Expand Up @@ -1274,3 +1276,38 @@ def test_when_regex_allow_list_is_all_empty_entries_then_results_are_kept():
)

assert filtered == results


@pytest.mark.parametrize(
"entities",
[["CREDIT_CARD"], ["CREDIT_CARD", "UNSUPPORTED_ENTITY"]],
)
def test_when_analyze_with_supported_entities_then_return_exact_results(
entities,
caplog,
):
analyzer_engine = AnalyzerEngine(
registry=RecognizerRegistry([CreditCardRecognizer()]),
nlp_engine=NoOpNlpEngine(models=[{"lang_code": "en", "model_name": "no_op"}]),
)
with caplog.at_level("WARNING", logger="presidio-analyzer"):
results = analyzer_engine.analyze(
text="My name is David and his number is 4095-2609-9393-4932",
entities=entities,
language="en",
)

assert len(results) == 1
assert_result(results[0], "CREDIT_CARD", 35, 54, 1.0)
warnings = [
record.message for record in caplog.records if record.levelname == "WARNING"
]
if "UNSUPPORTED_ENTITY" in entities:
assert len(warnings) == 1
assert "UNSUPPORTED_ENTITY" in warnings[0]
assert "language : en" in warnings[0]
assert "deprecated" in warnings[0]
assert "will raise an error in a future version" in warnings[0]
assert "get_supported_entities" in warnings[0]
else:
assert warnings == []
79 changes: 79 additions & 0 deletions presidio-analyzer/tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# ruff: noqa: D103,E501
"""Tests for the analyzer REST API server."""

from unittest.mock import patch

import pytest
from app import create_app
from presidio_analyzer import AnalyzerEngine, RecognizerRegistry
from presidio_analyzer.nlp_engine import NoOpNlpEngine
from presidio_analyzer.predefined_recognizers import IbanRecognizer


@pytest.fixture
def client():
engine = AnalyzerEngine(
registry=RecognizerRegistry([IbanRecognizer()]),
nlp_engine=NoOpNlpEngine(models=[{"lang_code": "en", "model_name": "no_op"}]),
)
with patch("app.AnalyzerEngineProvider") as provider:
provider.return_value.create_engine.return_value = engine
app = create_app()
app.testing = True
yield app.test_client()


@pytest.mark.parametrize("entities", [["IBAN_CODE"], ["IT_FISCAL_CODE", "IBAN_CODE"]])
def test_given_supported_entities_then_return_exact_results(client, entities, caplog):
with caplog.at_level("WARNING", logger="presidio-analyzer"):
response = client.post(
"/analyze",
json={
"text": "codice fiscale RSSMRA85M01H501Q, IBAN IT60X0542811101000000123456",
"language": "en",
"entities": entities,
},
)

assert response.status_code == 200
assert response.get_json() == [
{
"entity_type": "IBAN_CODE",
"start": 38,
"end": 65,
"score": 1.0,
"analysis_explanation": None,
}
]
warnings = [
record.message for record in caplog.records if record.levelname == "WARNING"
]
if "IT_FISCAL_CODE" in entities:
assert len(warnings) == 1
assert "IT_FISCAL_CODE" in warnings[0]
assert "language : en" in warnings[0]
assert "deprecated" in warnings[0]
assert "will raise an error in a future version" in warnings[0]
else:
assert warnings == []


def test_given_no_matching_entities_then_return_500(client):
response = client.post(
"/analyze",
json={"text": "test", "language": "en", "entities": ["UNSUPPORTED_ENTITY"]},
)

assert response.status_code == 500
assert response.get_json() == {
"error": "No matching recognizers were found to serve the request."
}


def test_given_unsupported_language_for_supported_entities_then_return_500(client):
response = client.get("/supportedentities?language=he")

assert response.status_code == 500
assert response.get_json() == {
"error": "No matching recognizers were found to serve the request."
}
63 changes: 62 additions & 1 deletion presidio-analyzer/tests/test_recognizer_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,14 @@ def test_when_get_recognizers_then_return_all_fields(mock_recognizer_registry):

def test_when_get_recognizers_one_language_then_return_one_entity(
mock_recognizer_registry,
caplog,
):
registry = mock_recognizer_registry
recognizers = registry.get_recognizers(language="de", entities=["PERSON"])
assert len(recognizers) == 1
assert recognizers == [registry.recognizers[1]]
assert not any(
"Ignoring unsupported entities" in record.message for record in caplog.records
)


def test_when_get_recognizers_unsupported_language_then_return(
Expand All @@ -89,6 +93,63 @@ def test_when_get_recognizers_unsupported_language_then_return(
registry.get_recognizers(language="brrrr", entities=["PERSON"])


@pytest.mark.parametrize("unsupported_entity", ["UNSUPPORTED_ENTITY", "ADDRESS"])
def test_when_get_recognizers_with_unsupported_entity_then_warn_and_return_supported(
mock_recognizer_registry,
caplog,
unsupported_entity,
):
registry = mock_recognizer_registry
with caplog.at_level("WARNING", logger="presidio-analyzer"):
recognizers = registry.get_recognizers(
language="en", entities=["PERSON", unsupported_entity]
)

assert recognizers == [registry.recognizers[0]]
warnings = [
record.message for record in caplog.records if record.levelname == "WARNING"
]
assert len(warnings) == 1
assert unsupported_entity in warnings[0]
assert "language : en" in warnings[0]
assert "deprecated" in warnings[0]
assert "future version" in warnings[0]
assert "raise" in warnings[0]
assert "get_supported_entities" in warnings[0]


@pytest.mark.parametrize("entity", ["ADDRESS", "UNSUPPORTED_ENTITY"])
def test_when_get_recognizers_without_matching_entities_then_raise(
mock_recognizer_registry,
entity,
):
registry = mock_recognizer_registry
with pytest.raises(ValueError) as err:
registry.get_recognizers(language="en", entities=[entity])

assert str(err.value) == "No matching recognizers were found to serve the request."


def test_when_get_recognizers_with_ad_hoc_recognizer_then_no_error(
mock_recognizer_registry,
caplog,
):
registry = mock_recognizer_registry
ad_hoc_recognizer = create_mock_pattern_recognizer(
"en", "UNSUPPORTED_ENTITY", "ad hoc"
)
recognizers = registry.get_recognizers(
language="en",
entities=["UNSUPPORTED_ENTITY"],
ad_hoc_recognizers=[ad_hoc_recognizer],
)

assert recognizers == [ad_hoc_recognizer]
assert not any(
"Ignoring unsupported entities" in record.message for record in caplog.records
)


def test_when_get_recognizers_specific_language_and_entity_then_return_one_result(
mock_recognizer_registry,
):
Expand Down