From 605dad9ba0aeae795a2988df93a888a12e304700 Mon Sep 17 00:00:00 2001 From: Stefano Braghin <527806+stefano81@users.noreply.github.com> Date: Sat, 19 Sep 2026 23:39:35 +0100 Subject: [PATCH 1/5] fix: align with presidio API Signed-off-by: Stefano Braghin <527806+stefano81@users.noreply.github.com> --- pyproject.toml | 1 - .../classification/unstructured/presidio.py | 16 ++++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 59d670f..8f8beed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,6 @@ dependencies = [ "pyarrow==24.0.0", "python-dotenv==1.2.2", "pytz==2024.1", - # "safetensors==0.4.4", "scipy>=1.17.1", "spacy==3.8.14", "SPARQLWrapper==2.0.0", diff --git a/src/risk_assessment/classification/unstructured/presidio.py b/src/risk_assessment/classification/unstructured/presidio.py index 9378087..d130e00 100644 --- a/src/risk_assessment/classification/unstructured/presidio.py +++ b/src/risk_assessment/classification/unstructured/presidio.py @@ -20,16 +20,20 @@ def __init__( default_score_threshold: float = 0, supported_languages: list[str] | None = None, ): + super().__init__(type_mapping) if supported_languages is None: supported_languages = ["en"] self.type_mapping = type_mapping - self.analyzer = AnalyzerEngine( - registry=registry, - nlp_engine=nlp_engine, - default_score_threshold=default_score_threshold, - supported_languages=supported_languages, - ) + kwargs: dict = { + "default_score_threshold": default_score_threshold, + "supported_languages": supported_languages, + } + if registry is not None: + kwargs["registry"] = registry + if nlp_engine is not None: + kwargs["nlp_engine"] = nlp_engine + self.analyzer = AnalyzerEngine(**kwargs) def extract(self, text: str) -> list[Entity]: return [ From b852c7784571249d9c4ceb16e7f2b480b4a840fa Mon Sep 17 00:00:00 2001 From: Stefano Braghin <527806+stefano81@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:07:27 +0100 Subject: [PATCH 2/5] chore: improve pre-commit dependencies Signed-off-by: Stefano Braghin <527806+stefano81@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8c62b64..2450d6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: args: [ --pytest-test-first ] # - id: check-added-large-files - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.16.3 + rev: v0.16.8 hooks: - id: ruff # Run the formatter @@ -32,12 +32,12 @@ repos: exclude: docs/|sphinx/ - repo: https://github.com/allganize/ty-pre-commit # Ty version. - rev: "v0.0.72" + rev: "v0.0.82" hooks: # Run the type checker. - id: ty-check - repo: https://github.com/abravalheri/validate-pyproject - rev: 'v0.25' + rev: '0.26' hooks: - id: validate-pyproject # Optional extra validations from SchemaStore: From 431e940551db6a78e31f2b95532d424ea776c860 Mon Sep 17 00:00:00 2001 From: Stefano Braghin <527806+stefano81@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:16:50 +0100 Subject: [PATCH 3/5] fix: issue with type Signed-off-by: Stefano Braghin <527806+stefano81@users.noreply.github.com> --- src/risk_assessment/readi/text_tokenizer.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/risk_assessment/readi/text_tokenizer.py b/src/risk_assessment/readi/text_tokenizer.py index b3ba002..1886967 100644 --- a/src/risk_assessment/readi/text_tokenizer.py +++ b/src/risk_assessment/readi/text_tokenizer.py @@ -17,10 +17,11 @@ import warnings from abc import ABC, abstractmethod +from typing import cast import MeCab from nltk.tokenize import WordPunctTokenizer -from transformers import AutoTokenizer +from transformers import AutoTokenizer, PreTrainedTokenizer, PreTrainedTokenizerFast from risk_assessment.readi.sentence_tokenizer import SentenceTokenizer @@ -151,7 +152,10 @@ def __init__(self, model_name: str = "FacebookAI/roberta-base", device: str = "c device: Device string for downstream model inference. Defaults to ``"cpu"``. """ self.device = device - self.tokenizer = AutoTokenizer.from_pretrained(model_name) # nosec + self.tokenizer: PreTrainedTokenizer | PreTrainedTokenizerFast = cast( + PreTrainedTokenizer | PreTrainedTokenizerFast, + AutoTokenizer.from_pretrained(model_name), # nosec + ) def span_tokenize(self, text: str) -> list[tuple[int, int]]: tokenized_text = self.tokenizer(text) From 60dccf32485dfde19b103c255f88a29f85461a00 Mon Sep 17 00:00:00 2001 From: Stefano Braghin <527806+stefano81@users.noreply.github.com> Date: Sun, 20 Sep 2026 22:22:52 +0100 Subject: [PATCH 4/5] fix: improve test coverage Signed-off-by: Stefano Braghin <527806+stefano81@users.noreply.github.com> --- pyproject.toml | 2 +- .../unstructured/test_presidio.py | 302 ++++++++++++++++++ 2 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 tests/classification/unstructured/test_presidio.py diff --git a/pyproject.toml b/pyproject.toml index 8f8beed..61cb1aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,6 +177,6 @@ omit = [ [tool.coverage.report] # Regress guard: overall coverage must not drop below this. # Update this value when coverage genuinely improves. -fail_under = 33 +fail_under = 80 show_missing = true skip_covered = false diff --git a/tests/classification/unstructured/test_presidio.py b/tests/classification/unstructured/test_presidio.py new file mode 100644 index 0000000..ea2a327 --- /dev/null +++ b/tests/classification/unstructured/test_presidio.py @@ -0,0 +1,302 @@ +"""Tests for PresidioEntityExtractor. + +Since presidio_analyzer is an optional dependency, the module is patched at +import time so these tests run regardless of whether the library is installed. +""" + +import sys +from types import ModuleType +from unittest.mock import MagicMock, patch + +import pytest + +# --------------------------------------------------------------------------- +# Build a minimal presidio_analyzer stub so the conditional import in +# presidio.py resolves to our mock classes, not ModuleNotFoundError. +# --------------------------------------------------------------------------- + +_presidio_stub = ModuleType("presidio_analyzer") +_nlp_engine_stub = ModuleType("presidio_analyzer.nlp_engine") + + +class _RecognizerResult: + """Minimal stand-in for presidio_analyzer.RecognizerResult.""" + + def __init__(self, entity_type: str, start: int, end: int, score: float): + self.entity_type = entity_type + self.start = start + self.end = end + self.score = score + + +class _RecognizerRegistry: + pass + + +class _NlpEngine: + pass + + +class _AnalyzerEngine: + """Minimal stand-in for presidio_analyzer.AnalyzerEngine.""" + + def __init__(self, **kwargs): + self._kwargs = kwargs + + def analyze(self, text: str, language: str) -> list[_RecognizerResult]: + return [] + + +setattr(_presidio_stub, "AnalyzerEngine", _AnalyzerEngine) +setattr(_presidio_stub, "RecognizerRegistry", _RecognizerRegistry) +setattr(_presidio_stub, "RecognizerResult", _RecognizerResult) +setattr(_nlp_engine_stub, "NlpEngine", _NlpEngine) + +# Register the stubs before importing the module under test. +sys.modules.setdefault("presidio_analyzer", _presidio_stub) +sys.modules.setdefault("presidio_analyzer.nlp_engine", _nlp_engine_stub) + +# Now it is safe to import – the try/except in presidio.py will succeed. +from risk_assessment.classification.unstructured import Entity # noqa: E402 +from risk_assessment.classification.unstructured.presidio import PresidioEntityExtractor # noqa: E402 + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +TYPE_MAPPING: dict[str, str] = { + "PERSON": "Person", + "EMAIL_ADDRESS": "Email", + "PHONE_NUMBER": "Phone", + "LOCATION": "Location", +} + + +def _make_extractor(**kwargs) -> PresidioEntityExtractor: + return PresidioEntityExtractor(type_mapping=TYPE_MAPPING, **kwargs) + + +def _stub_result(entity_type: str, start: int, end: int, score: float = 0.85) -> _RecognizerResult: + return _RecognizerResult(entity_type=entity_type, start=start, end=end, score=score) + + +# --------------------------------------------------------------------------- +# Construction +# --------------------------------------------------------------------------- + + +def test_default_construction(): + extractor = _make_extractor() + assert extractor.type_mapping == TYPE_MAPPING + assert extractor.analyzer is not None + + +def test_name_constant(): + assert PresidioEntityExtractor.NAME == "PRESIDIO" + + +def test_custom_score_threshold_is_forwarded(): + """AnalyzerEngine must receive the custom threshold.""" + with patch("risk_assessment.classification.unstructured.presidio.AnalyzerEngine") as mock_engine_cls: + mock_engine_cls.return_value = MagicMock() + _make_extractor(default_score_threshold=0.5) + call_kwargs = mock_engine_cls.call_args.kwargs + assert call_kwargs["default_score_threshold"] == 0.5 + + +def test_default_language_is_english(): + """supported_languages defaults to ['en'] when not supplied.""" + with patch("risk_assessment.classification.unstructured.presidio.AnalyzerEngine") as mock_engine_cls: + mock_engine_cls.return_value = MagicMock() + _make_extractor() + call_kwargs = mock_engine_cls.call_args.kwargs + assert call_kwargs["supported_languages"] == ["en"] + + +def test_custom_supported_languages(): + with patch("risk_assessment.classification.unstructured.presidio.AnalyzerEngine") as mock_engine_cls: + mock_engine_cls.return_value = MagicMock() + _make_extractor(supported_languages=["en", "de"]) + call_kwargs = mock_engine_cls.call_args.kwargs + assert call_kwargs["supported_languages"] == ["en", "de"] + + +def test_registry_forwarded_when_provided(): + registry = _RecognizerRegistry() + with patch("risk_assessment.classification.unstructured.presidio.AnalyzerEngine") as mock_engine_cls: + mock_engine_cls.return_value = MagicMock() + _make_extractor(registry=registry) + call_kwargs = mock_engine_cls.call_args.kwargs + assert call_kwargs["registry"] is registry + + +def test_registry_omitted_when_none(): + with patch("risk_assessment.classification.unstructured.presidio.AnalyzerEngine") as mock_engine_cls: + mock_engine_cls.return_value = MagicMock() + _make_extractor(registry=None) + call_kwargs = mock_engine_cls.call_args.kwargs + assert "registry" not in call_kwargs + + +def test_nlp_engine_forwarded_when_provided(): + engine = _NlpEngine() + with patch("risk_assessment.classification.unstructured.presidio.AnalyzerEngine") as mock_engine_cls: + mock_engine_cls.return_value = MagicMock() + _make_extractor(nlp_engine=engine) + call_kwargs = mock_engine_cls.call_args.kwargs + assert call_kwargs["nlp_engine"] is engine + + +def test_nlp_engine_omitted_when_none(): + with patch("risk_assessment.classification.unstructured.presidio.AnalyzerEngine") as mock_engine_cls: + mock_engine_cls.return_value = MagicMock() + _make_extractor(nlp_engine=None) + call_kwargs = mock_engine_cls.call_args.kwargs + assert "nlp_engine" not in call_kwargs + + +# --------------------------------------------------------------------------- +# extract() +# --------------------------------------------------------------------------- + + +def test_extract_returns_empty_list_for_no_results(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock(return_value=[]) + assert extractor.extract("no entities here") == [] + + +def test_extract_returns_entity_list(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock( + return_value=[ + _stub_result("PERSON", 11, 19, score=0.9), + _stub_result("EMAIL_ADDRESS", 23, 42, score=0.95), + ] + ) + entities = extractor.extract("Hello I'm John Doe at jdoe@example.com") + assert len(entities) == 2 + + +def test_extract_calls_analyzer_with_english(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock(return_value=[]) + extractor.extract("some text") + extractor.analyzer.analyze.assert_called_once_with("some text", language="en") + + +def test_extract_entity_spans_are_correct(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock(return_value=[_stub_result("PERSON", 5, 13)]) + entities = extractor.extract("Hi, John Doe") + assert entities[0].start == 5 + assert entities[0].end == 13 + + +def test_extract_entity_confidence_is_forwarded(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock(return_value=[_stub_result("PERSON", 0, 8, score=0.77)]) + entities = extractor.extract("John Doe") + assert entities[0].confidence == pytest.approx(0.77) + + +def test_extract_source_is_presidio(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock(return_value=[_stub_result("PERSON", 0, 8)]) + entities = extractor.extract("John Doe") + assert entities[0].source == frozenset(["PRESIDIO"]) + + +# --------------------------------------------------------------------------- +# Type mapping +# --------------------------------------------------------------------------- + + +def test_known_type_is_mapped(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock(return_value=[_stub_result("PERSON", 0, 8)]) + entities = extractor.extract("John Doe") + assert entities[0].entity_type == "Person" + + +def test_unknown_type_passes_through_unchanged(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock(return_value=[_stub_result("CRYPTO", 0, 5)]) + entities = extractor.extract("1BvBM") + assert entities[0].entity_type == "CRYPTO" + + +def test_all_mapped_types_are_converted(): + presidio_to_standard = { + "PERSON": "Person", + "EMAIL_ADDRESS": "Email", + "PHONE_NUMBER": "Phone", + "LOCATION": "Location", + } + extractor = _make_extractor() + for presidio_type, expected_type in presidio_to_standard.items(): + extractor.analyzer.analyze = MagicMock(return_value=[_stub_result(presidio_type, 0, 5)]) + entities = extractor.extract("dummy") + assert entities[0].entity_type == expected_type, f"{presidio_type} should map to {expected_type}" + + +def test_empty_type_mapping_returns_raw_types(): + extractor = PresidioEntityExtractor(type_mapping={}) + extractor.analyzer.analyze = MagicMock(return_value=[_stub_result("PERSON", 0, 8)]) + entities = extractor.extract("John Doe") + assert entities[0].entity_type == "PERSON" + + +# --------------------------------------------------------------------------- +# _build_entity() +# --------------------------------------------------------------------------- + + +def test_build_entity_returns_entity_dataclass(): + extractor = _make_extractor() + result = _stub_result("PERSON", 3, 11, score=0.88) + entity = extractor._build_entity(result) + assert isinstance(entity, Entity) + assert entity.start == 3 + assert entity.end == 11 + assert entity.entity_type == "Person" + assert entity.confidence == pytest.approx(0.88) + assert entity.source == frozenset(["PRESIDIO"]) + + +def test_build_entity_score_zero(): + extractor = _make_extractor() + result = _stub_result("PERSON", 0, 4, score=0.0) + entity = extractor._build_entity(result) + assert entity.confidence == pytest.approx(0.0) + + +# --------------------------------------------------------------------------- +# Multiple entities in one extraction +# --------------------------------------------------------------------------- + + +def test_extract_multiple_entity_types(): + extractor = _make_extractor() + extractor.analyzer.analyze = MagicMock( + return_value=[ + _stub_result("PERSON", 0, 8, score=0.9), + _stub_result("EMAIL_ADDRESS", 12, 30, score=0.95), + _stub_result("PHONE_NUMBER", 34, 46, score=0.8), + ] + ) + entities = extractor.extract("John Doe – jdoe@example.com – 555-123-4567") + types = {e.entity_type for e in entities} + assert types == {"Person", "Email", "Phone"} + + +def test_extract_preserves_order(): + extractor = _make_extractor() + results = [ + _stub_result("PERSON", 0, 4), + _stub_result("LOCATION", 10, 16), + _stub_result("EMAIL_ADDRESS", 20, 38), + ] + extractor.analyzer.analyze = MagicMock(return_value=results) + entities = extractor.extract("Ann lives in Paris. ann@example.com") + assert [e.start for e in entities] == [0, 10, 20] From dada129ffae73296f79fc14af2a5f54ddcc0e0c8 Mon Sep 17 00:00:00 2001 From: Stefano Braghin <527806+stefano81@users.noreply.github.com> Date: Sun, 20 Sep 2026 23:52:04 +0100 Subject: [PATCH 5/5] fix: improve test coverage Signed-off-by: Stefano Braghin <527806+stefano81@users.noreply.github.com> --- pyproject.toml | 3 ++ uv.lock | 132 ++++++++++++++++++++++++++----------------------- 2 files changed, 74 insertions(+), 61 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 61cb1aa..1753a81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,9 @@ rest = [ # flair = [ # "flair>=0.15.1", # ] +presidio = [ + "presidio-analyzer==2.2.364", +] dev = [ "bandit", "faker", diff --git a/uv.lock b/uv.lock index 2431d3c..57d963c 100644 --- a/uv.lock +++ b/uv.lock @@ -286,8 +286,7 @@ name = "blis" version = "1.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873, upload-time = "2025-11-17T12:28:30.511Z" } wheels = [ @@ -759,8 +758,7 @@ dependencies = [ { name = "httpx" }, { name = "huggingface-hub" }, { name = "multiprocess" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "packaging" }, { name = "pandas" }, { name = "pyarrow" }, @@ -875,8 +873,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cramjam" }, { name = "fsspec" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "packaging" }, { name = "pandas" }, ] @@ -1861,11 +1858,6 @@ wheels = [ name = "numpy" version = "2.4.6" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, @@ -1920,41 +1912,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, ] -[[package]] -name = "numpy" -version = "2.5.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.12' and sys_platform == 'win32'", - "python_full_version >= '3.12' and sys_platform == 'emscripten'", - "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] -sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/50/8fdbb16af64895706a45f06a4068e29db732ec180f3c1375f14123359138/numpy-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9", size = 16994982, upload-time = "2026-09-06T16:24:29.244Z" }, - { url = "https://files.pythonhosted.org/packages/60/39/789131c1188c078dcb3a1692e72e1e050c68b88ffe72c9ccaac9bcd7a9cd/numpy-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c", size = 12009327, upload-time = "2026-09-06T16:24:32.491Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/a312e95696e5f601914dd8b6dd844692ba61670807417e24b68e337b5c70/numpy-2.5.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0", size = 5445405, upload-time = "2026-09-06T16:24:35.071Z" }, - { url = "https://files.pythonhosted.org/packages/30/d0/5623a1707ed4fe16e3909fe3cf5ee3da004ae677ad23d83bbf3adf1a6faf/numpy-2.5.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e", size = 6783213, upload-time = "2026-09-06T16:24:37.253Z" }, - { url = "https://files.pythonhosted.org/packages/f1/32/84146fc020ad3c25f805f70ab60da46fe3c540a21369754a7e4369754b6f/numpy-2.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58", size = 15687872, upload-time = "2026-09-06T16:24:39.751Z" }, - { url = "https://files.pythonhosted.org/packages/65/af/aa78d1a88805456e212b65461354cd943197fb9acecc4c90fd12295123a3/numpy-2.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3", size = 16717410, upload-time = "2026-09-06T16:24:42.745Z" }, - { url = "https://files.pythonhosted.org/packages/3b/24/faa79d865e69a97ba17473b23a1b74094b2259c03e820c70297293b9ea49/numpy-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff", size = 17040975, upload-time = "2026-09-06T16:24:45.961Z" }, - { url = "https://files.pythonhosted.org/packages/62/4a/8877e629445a7176297dffcaf9c485faa96a95d81728a62521ad55bd4c0f/numpy-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034", size = 18476479, upload-time = "2026-09-06T16:24:49.35Z" }, - { url = "https://files.pythonhosted.org/packages/c8/db/35e1c2d38b04cbd5b731f9d71495e055e813197669d22b612f11748d2ff9/numpy-2.5.3-cp312-cp312-win32.whl", hash = "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4", size = 6133378, upload-time = "2026-09-06T16:24:51.915Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/accf6d4f0c80c5d9ba9735d6b1550e444180599f34dec69ca01360f717ad/numpy-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def", size = 12567828, upload-time = "2026-09-06T16:24:54.255Z" }, - { url = "https://files.pythonhosted.org/packages/22/43/1764aff32e4652526ae2f71fa8b3efd8d25c8a3d6926914454e47138ed1e/numpy-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034", size = 10485432, upload-time = "2026-09-06T16:24:57.278Z" }, - { url = "https://files.pythonhosted.org/packages/79/e5/8fb89cd46d14e35699d13bf943a5f5f441ecee8667120a1f6105ab89e349/numpy-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034", size = 16991061, upload-time = "2026-09-06T16:25:00.411Z" }, - { url = "https://files.pythonhosted.org/packages/2f/06/9dc9e48b5e5e941c8b10350c5ff2d721da42a20517d911d15544246775ff/numpy-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e", size = 12003676, upload-time = "2026-09-06T16:25:03.475Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2a/98282aa5b8f58b1157d440bb6282eed47e3632a5de53a714fbab17e659fe/numpy-2.5.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09", size = 5439695, upload-time = "2026-09-06T16:25:05.978Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f9/b6533d777be9d6ffd29dc1be0867e563e6e8cc9a220ff1b716adc317f060/numpy-2.5.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958", size = 6779395, upload-time = "2026-09-06T16:25:08.599Z" }, - { url = "https://files.pythonhosted.org/packages/73/85/735720d04ec197c5dcfacdfc9922667c7f1f5f496a279b7ba4d7c74c4cc7/numpy-2.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b", size = 15681750, upload-time = "2026-09-06T16:25:11.173Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1b/3b16a9bc514a440a7a0883684111dcb1ef1aee960af2ca95da8fc775f124/numpy-2.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321", size = 16708577, upload-time = "2026-09-06T16:25:14.171Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/386f397831b07328b639c96c5b62719346cf4baf07c68d927239752b1534/numpy-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231", size = 17042047, upload-time = "2026-09-06T16:25:17.582Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3e/a700ecbf36e85ae8328fd3b0e12eeddc22ed6358a64cb2bd913e0d195d65/numpy-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0", size = 18465724, upload-time = "2026-09-06T16:25:20.949Z" }, - { url = "https://files.pythonhosted.org/packages/41/ee/38e785e88a4045f6ad1d1f2808dcdfafdca48c760260c0587bf171e29fc9/numpy-2.5.3-cp313-cp313-win32.whl", hash = "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f", size = 6129003, upload-time = "2026-09-06T16:25:23.664Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ec/100f2b1794ede74a9b3d7ec6b9736927f56713414c1dfe19ab6c383494bf/numpy-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec", size = 12560965, upload-time = "2026-09-06T16:25:26.602Z" }, - { url = "https://files.pythonhosted.org/packages/80/b1/7dc825ca94c12acebbce4c37caa5e198695eb31424bc579679f32b1bb49d/numpy-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204", size = 10482343, upload-time = "2026-09-06T16:25:29.772Z" }, -] - [[package]] name = "nvidia-cublas" version = "13.1.1.3" @@ -2112,8 +2069,7 @@ name = "opt-einsum" version = "3.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/bf/9257e53a0e7715bc1127e15063e831f076723c6cd60985333a1c18878fb8/opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549", size = 73951, upload-time = "2020-07-19T22:40:32.141Z" } wheels = [ @@ -2143,8 +2099,7 @@ name = "pandas" version = "3.0.6" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] @@ -2207,6 +2162,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, ] +[[package]] +name = "phonenumbers" +version = "9.0.39" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/d9/f73babd005dd7569a81789f4db9cc82efaf001af018d5752bf6d7ea14e19/phonenumbers-9.0.39.tar.gz", hash = "sha256:41a7ff0257d5a7a096a8e263da7efc0a96a6162c84ba64af2ebf8972d09e70e1", size = 2310991, upload-time = "2026-09-10T11:26:52.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/53/238c99f028b9c638ceae8218a33904e4b84d60e1c3bd707c0ff231b388cf/phonenumbers-9.0.39-py2.py3-none-any.whl", hash = "sha256:d83b878d0188c81a5f5e8572cedcc7262bd0ea3806eb7c8e35d8666d262a8af0", size = 2594744, upload-time = "2026-09-10T11:26:49.561Z" }, +] + [[package]] name = "pip" version = "26.2.1" @@ -2286,6 +2250,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a7/32a4903019d936a2316fdd330bedddac287ac26326107d24fb76a1fbc60a/preshed-3.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:35d6c5acb3ee3b12b87a551913063f0cec784055c2af16e028c19fe875f079d0", size = 108497, upload-time = "2026-03-23T08:56:55.816Z" }, ] +[[package]] +name = "presidio-analyzer" +version = "2.2.364" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "numpy" }, + { name = "phonenumbers" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "spacy" }, + { name = "tldextract" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/50/408a41cf99d076107d3823d514bb6408e249132418f068673cb807839ce5/presidio_analyzer-2.2.364.tar.gz", hash = "sha256:51f27bacf199fb9777ade9552c0f0111e00a1d73cfe2d4797fc05c998de1e0b6", size = 154117, upload-time = "2026-07-22T07:54:36.264Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/15/44a64276cd5c45485716314addfb7966c24f61034d8ff8e9a78d13d0afbe/presidio_analyzer-2.2.364-py3-none-any.whl", hash = "sha256:0a9eeb60ccc416c505367b4989d056becb84ef082703ee3361c046fb75941739", size = 266299, upload-time = "2026-07-22T07:54:34.868Z" }, +] + [[package]] name = "prometheus-client" version = "0.26.0" @@ -2823,6 +2806,9 @@ dev = [ { name = "ty" }, { name = "ucimlrepo" }, ] +presidio = [ + { name = "presidio-analyzer" }, +] rest = [ { name = "fastapi" }, ] @@ -2843,6 +2829,7 @@ requires-dist = [ { name = "pandas", specifier = "==3.0.6" }, { name = "pip", specifier = ">=26.0.0" }, { name = "prek", marker = "extra == 'dev'" }, + { name = "presidio-analyzer", marker = "extra == 'presidio'", specifier = "==2.2.364" }, { name = "pyarrow", specifier = "==24.0.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'" }, { name = "pytest-cov", marker = "extra == 'dev'" }, @@ -2864,7 +2851,7 @@ requires-dist = [ { name = "urllib3", specifier = "==2.7.0" }, { name = "word2number", specifier = "==1.1" }, ] -provides-extras = ["dev", "rest"] +provides-extras = ["dev", "presidio", "rest"] [[package]] name = "referencing" @@ -2951,6 +2938,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] +[[package]] +name = "requests-file" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/f8/5dc70102e4d337063452c82e1f0d95e39abfe67aa222ed8a5ddeb9df8de8/requests_file-3.0.1.tar.gz", hash = "sha256:f14243d7796c588f3521bd423c5dea2ee4cc730e54a3cac9574d78aca1272576", size = 6967, upload-time = "2025-10-20T18:56:42.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d5/de8f089119205a09da657ed4784c584ede8381a0ce6821212a6d4ca47054/requests_file-3.0.1-py2.py3-none-any.whl", hash = "sha256:d0f5eb94353986d998f80ac63c7f146a307728be051d4d1cd390dbdb59c10fa2", size = 4514, upload-time = "2025-10-20T18:56:41.184Z" }, +] + [[package]] name = "rfc3339-validator" version = "0.1.4" @@ -3121,7 +3120,7 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -3177,7 +3176,7 @@ resolution-markers = [ "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235, upload-time = "2026-08-21T23:28:50.599Z" } wheels = [ @@ -3270,8 +3269,7 @@ dependencies = [ { name = "cymem" }, { name = "jinja2" }, { name = "murmurhash" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "packaging" }, { name = "preshed" }, { name = "pydantic" }, @@ -3399,8 +3397,7 @@ dependencies = [ { name = "emoji" }, { name = "huggingface-hub" }, { name = "networkx" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "platformdirs" }, { name = "protobuf" }, { name = "requests" }, @@ -3480,8 +3477,7 @@ dependencies = [ { name = "confection" }, { name = "cymem" }, { name = "murmurhash" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "packaging" }, { name = "preshed" }, { name = "pydantic" }, @@ -3542,6 +3538,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" }, ] +[[package]] +name = "tldextract" +version = "5.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "idna" }, + { name = "requests" }, + { name = "requests-file" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/a9/ed5d3be29bfaf90c00b7159d3884b311f3880b55833d1c7be764164dc288/tldextract-5.3.2.tar.gz", hash = "sha256:c017431bc0800f2d3d1b57cce36e06668f0930f60a6d8c4615d4e2b8da298fa9", size = 191971, upload-time = "2026-08-08T20:53:13.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/3b/930310b274dfe3b9a4fe64ef8b24e5faf376c9a6f94bba6fc6e438058598/tldextract-5.3.2-py3-none-any.whl", hash = "sha256:6c90d2a259f5c89f4fcf01f97af15708416a59ffedddff006d67222ab30d0fb0", size = 106415, upload-time = "2026-08-08T20:53:12.675Z" }, +] + [[package]] name = "tokenizers" version = "0.23.2" @@ -3684,8 +3695,7 @@ version = "5.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "packaging" }, { name = "pyyaml" }, { name = "regex" },