Skip to content

feat: add FastAPI anonymizer server - #2039

Open
ynachiket wants to merge 11 commits into
data-privacy-stack:mainfrom
ynachiket:ynachiket/fastapi-anonymizer-slice-1769-20260525
Open

feat: add FastAPI anonymizer server#2039
ynachiket wants to merge 11 commits into
data-privacy-stack:mainfrom
ynachiket:ynachiket/fastapi-anonymizer-slice-1769-20260525

Conversation

@ynachiket

@ynachiket ynachiket commented May 25, 2026

Copy link
Copy Markdown
Contributor

Change

Adds an optional FastAPI server for presidio-anonymizer as a first narrow slice of #1769. The new fastapi_app.py mirrors the existing Flask anonymizer REST endpoints:

  • GET /health
  • POST /anonymize
  • POST /deanonymize
  • GET /anonymizers
  • GET /deanonymizers

The existing Flask app and Docker entrypoint are unchanged. FastAPI and Uvicorn are exposed through a new optional fastapi extra, and the README includes a minimal run command.

Validation

  • /tmp/presidio-fastapi-1769-py312/bin/python -m py_compile presidio-anonymizer/fastapi_app.py presidio-anonymizer/tests/test_fastapi_app.py
  • cd presidio-anonymizer && /tmp/presidio-fastapi-1769-py312/bin/python -m pytest tests/test_fastapi_app.py -q -> 4 passed
  • cd presidio-anonymizer && /tmp/presidio-fastapi-1769-py312/bin/ruff check fastapi_app.py tests/test_fastapi_app.py
  • git diff --check

Closes #1769? No, this is intentionally a first anonymizer-only slice toward the broader multi-service request.

@ynachiket
ynachiket requested a review from a team as a code owner May 25, 2026 12:01
@omri374
omri374 requested a review from Copilot May 25, 2026 13:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional FastAPI-based REST server for presidio-anonymizer, intended to mirror the existing Flask anonymizer service endpoints as an initial slice toward #1769.

Changes:

  • Introduces fastapi_app.py implementing /health, /anonymize, /deanonymize, /anonymizers, and /deanonymizers in FastAPI.
  • Adds a new fastapi optional dependency extra (FastAPI + Uvicorn).
  • Adds a minimal README run snippet and a new test module for the FastAPI app.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
presidio-anonymizer/fastapi_app.py New FastAPI server implementation mirroring the existing Flask routes and error shapes.
presidio-anonymizer/pyproject.toml Adds fastapi optional-dependency extra for running the FastAPI server.
presidio-anonymizer/README.md Documents how to run the optional FastAPI server with Uvicorn.
presidio-anonymizer/tests/test_fastapi_app.py Adds initial tests for the FastAPI server endpoints and error response shape.

Comment on lines +52 to +56
@self.app.post("/anonymize")
def anonymize(content: dict[str, Any]) -> Response:
if not content:
raise HTTPException(status_code=400, detail="Invalid request json")

Comment on lines +6 to +10
import pytest

pytest.importorskip("httpx")
fastapi_testclient = pytest.importorskip("fastapi.testclient")
TestClient = fastapi_testclient.TestClient
spec = util.spec_from_file_location("presidio_anonymizer_fastapi_app", module_path)
module = util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.create_app()
Comment thread presidio-anonymizer/README.md Outdated
Comment on lines +208 to +215
The anonymizer package also includes a FastAPI server with the same REST endpoints
as the default Flask server. Install the optional dependencies and run it with
Uvicorn:

```sh
pip install "presidio-anonymizer[fastapi]"
uvicorn fastapi_app:app --host 0.0.0.0 --port 3000
```
Comment on lines +31 to +74
def test_anonymize_endpoint_returns_engine_response():
"""Anonymize endpoint returns the anonymizer engine JSON response."""
client = TestClient(_load_fastapi_app())

response = client.post(
"/anonymize",
json={
"text": "My name is Jane",
"analyzer_results": [
{
"start": 11,
"end": 15,
"score": 0.8,
"entity_type": "PERSON",
}
],
"anonymizers": {
"DEFAULT": {"type": "replace", "new_value": "<ANONYMIZED>"}
},
},
)

assert response.status_code == 200
assert response.json()["text"] == "My name is <ANONYMIZED>"


def test_empty_json_body_returns_flask_compatible_error_shape():
"""Empty JSON requests keep the existing error response shape."""
client = TestClient(_load_fastapi_app())

response = client.post("/anonymize", json={})

assert response.status_code == 400
assert response.json() == {"error": "Invalid request json"}


def test_anonymizers_endpoint_returns_supported_operators():
"""Anonymizers endpoint exposes built-in anonymizer operators."""
client = TestClient(_load_fastapi_app())

response = client.get("/anonymizers")

assert response.status_code == 200
assert "replace" in response.json()
@ynachiket

Copy link
Copy Markdown
Contributor Author

Addressed the Copilot review feedback in 52e7f57:

  • Parse FastAPI request JSON manually so missing/invalid JSON returns the Flask-compatible {"error": "Invalid request json"} response shape.
  • Reuse the import-time module.app in the FastAPI tests to avoid duplicate app factory initialization.
  • Added coverage for missing/invalid JSON, /deanonymize, and /deanonymizers.
  • Added fastapi and httpx to the anonymizer dev dependencies so the new FastAPI tests run in CI instead of being skipped.
  • Clarified that uvicorn fastapi_app:app is a source-checkout command from presidio-anonymizer/.

Validation:

  • /tmp/presidio-pdlc045-py312/bin/python -m py_compile presidio-anonymizer/fastapi_app.py presidio-anonymizer/tests/test_fastapi_app.py
  • PYTHONPATH=presidio-anonymizer /tmp/presidio-pdlc045-py312/bin/python -m pytest presidio-anonymizer/tests/test_fastapi_app.py -q -> 8 passed
  • git diff --check

GitHub checks had not been reported yet immediately after push.

SharonHart
SharonHart previously approved these changes Jun 7, 2026
Copilot AI review requested due to automatic review settings June 7, 2026 06:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment on lines +53 to +76
@self.app.post("/anonymize")
async def anonymize(request: Request) -> Response:
content = await self._json_request_body(request)

anonymizers_config = AppEntitiesConvertor.operators_config_from_json(
content.get("anonymizers")
)
if AppEntitiesConvertor.check_custom_operator(anonymizers_config):
raise HTTPException(
status_code=400, detail="Custom type anonymizer is not supported"
)

analyzer_results = AppEntitiesConvertor.analyzer_results_from_json(
content.get("analyzer_results")
)
anonymizer_result = self.anonymizer.anonymize(
text=content.get("text", ""),
analyzer_results=analyzer_results,
operators=anonymizers_config,
)
return Response(
content=anonymizer_result.to_json(), media_type="application/json"
)

Comment on lines +77 to +96
@self.app.post("/deanonymize")
async def deanonymize(request: Request) -> Response:
content = await self._json_request_body(request)

deanonymize_entities = AppEntitiesConvertor.deanonymize_entities_from_json(
content
)
deanonymize_config = AppEntitiesConvertor.operators_config_from_json(
content.get("deanonymizers")
)
deanonymized_response = self.deanonymize.deanonymize(
text=content.get("text", ""),
entities=deanonymize_entities,
operators=deanonymize_config,
)
return Response(
content=deanonymized_response.to_json(),
media_type="application/json",
)

Comment on lines +129 to +132
@self.app.exception_handler(Exception)
def server_error(_: Request, err: Exception) -> JSONResponse:
self.logger.error("A fatal error occurred during execution: %s", err)
return JSONResponse({"error": "Internal server error"}, status_code=500)
Copilot AI review requested due to automatic review settings June 7, 2026 12:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Comment on lines +53 to +75
@self.app.post("/anonymize")
async def anonymize(request: Request) -> Response:
content = await self._json_request_body(request)

anonymizers_config = AppEntitiesConvertor.operators_config_from_json(
content.get("anonymizers")
)
if AppEntitiesConvertor.check_custom_operator(anonymizers_config):
raise HTTPException(
status_code=400, detail="Custom type anonymizer is not supported"
)

analyzer_results = AppEntitiesConvertor.analyzer_results_from_json(
content.get("analyzer_results")
)
anonymizer_result = self.anonymizer.anonymize(
text=content.get("text", ""),
analyzer_results=analyzer_results,
operators=anonymizers_config,
)
return Response(
content=anonymizer_result.to_json(), media_type="application/json"
)
Comment on lines +77 to +96
@self.app.post("/deanonymize")
async def deanonymize(request: Request) -> Response:
content = await self._json_request_body(request)

deanonymize_entities = AppEntitiesConvertor.deanonymize_entities_from_json(
content
)
deanonymize_config = AppEntitiesConvertor.operators_config_from_json(
content.get("deanonymizers")
)
deanonymized_response = self.deanonymize.deanonymize(
text=content.get("text", ""),
entities=deanonymize_entities,
operators=deanonymize_config,
)
return Response(
content=deanonymized_response.to_json(),
media_type="application/json",
)

Comment thread presidio-anonymizer/pyproject.toml Outdated
Comment on lines +52 to +53
fastapi = "*"
httpx = "*"
Comment thread presidio-anonymizer/README.md Outdated
directory, install the optional dependencies and run it with Uvicorn:

```sh
pip install "presidio-anonymizer[fastapi]"
@ynachiket

Copy link
Copy Markdown
Contributor Author

Addressed the latest FastAPI review follow-ups in 61caa80:

  • Offloaded the synchronous anonymizer and deanonymizer engine calls with run_in_threadpool so the async endpoints do not block the event loop during engine work.
  • Switched the generic exception handler to logger.exception(...) so unexpected server errors keep stack traces in logs while preserving the existing {"error": "Internal server error"} response shape.
  • Pinned the anonymizer dev fastapi dependency to the same <1.0.0 range as the supported optional extra.
  • Updated the source-tree run instructions to install the local checkout with pip install -e ".[fastapi]" before running uvicorn fastapi_app:app.

Validation:

  • PYTHONPATH=presidio-anonymizer /tmp/presidio-pdlc053-py312/bin/python -m pytest presidio-anonymizer/tests/test_fastapi_app.py -q -> 8 passed
  • /tmp/presidio-pdlc053-py312/bin/python -m py_compile presidio-anonymizer/fastapi_app.py presidio-anonymizer/tests/test_fastapi_app.py
  • /tmp/presidio-pdlc053-py312/bin/python -m ruff check presidio-anonymizer/fastapi_app.py presidio-anonymizer/tests/test_fastapi_app.py
  • git diff --check

Copilot AI review requested due to automatic review settings June 18, 2026 06:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread presidio-anonymizer/fastapi_app.py Outdated
return server.app


app = create_app()
@ynachiket

Copy link
Copy Markdown
Contributor Author

Addressed the latest Copilot FastAPI factory feedback in 118b1e6:\n\n- Removed the module-level app = create_app() so importing fastapi_app.py no longer initializes engines or reconfigures logging.\n- Updated the direct __main__ runner and README Uvicorn command to construct the app through create_app / --factory.\n- Updated the FastAPI tests to explicitly create the app from the factory.\n\nValidation:\n- PYTHONPATH=presidio-anonymizer /tmp/presidio-pdlc053-py312/bin/python -m pytest presidio-anonymizer/tests/test_fastapi_app.py -q -> 8 passed\n- /tmp/presidio-pdlc053-py312/bin/python -m py_compile presidio-anonymizer/fastapi_app.py presidio-anonymizer/tests/test_fastapi_app.py\n- /tmp/presidio-pdlc053-py312/bin/python -m ruff check presidio-anonymizer/fastapi_app.py presidio-anonymizer/tests/test_fastapi_app.py\n- git diff --check

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR] FastAPI Implementation for all services

3 participants