diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 8aace3c..172d425 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -23,6 +23,12 @@ jobs: with: python-version: '3.11' + - name: Run tests + run: | + python -m pip install --upgrade pip + pip install -e '.[dev]' + pytest -q + - name: Build package run: | python -m pip install --upgrade pip build diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..28f92a1 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,34 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + pytest: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12'] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package + run: | + python -m pip install --upgrade pip + pip install -e '.[dev]' + + - name: Run tests + run: pytest -q diff --git a/.gitignore b/.gitignore index 510c0ce..fc528a7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ .pytest_cache __pycache__ +vault.json +*.vault.json +.venv diff --git a/README.md b/README.md index d6aa146..04cd0e5 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ Under the hood: 1. **Detection** — a hybrid engine combines deterministic regex patterns (emails, phones, credit cards with Luhn validation, SSNs, IPs, IBANs, URLs, AWS/API keys) with Microsoft Presidio + spaCy NER for - contextual entities (people, organizations, locations). + contextual entities (people, organizations, locations). If Presidio + can't load, it falls back to a HuggingFace transformer model + (`pip install "shieldprompt[ner]"`). 2. **Masking** — each detected value is stored in an in-memory vault and replaced with a stable token like `[PERSON_1]` or `[EMAIL_ADDRESS_2]`. 3. **Unmasking** — after the LLM responds, tokens are mapped back to the diff --git a/pyproject.toml b/pyproject.toml index 94b68fd..5993f7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "shieldprompt" -version = "0.1.4" +version = "0.1.5" description = "Privacy bridge for LLMs — mask PII before it leaves your machine." readme = "README.md" license = {text = "MIT"} @@ -27,7 +27,7 @@ dependencies = [ ] [project.optional-dependencies] -ner = ["transformers>=4.30.0", "torch>=2.0.0", "span-marker>=1.5.0"] +ner = ["transformers>=4.30.0", "torch>=2.0.0"] fastapi = ["fastapi>=0.100.0", "starlette>=0.27.0"] all = ["shieldprompt[ner,fastapi]"] dev = [ @@ -44,8 +44,9 @@ pii = "shieldprompt.cli:pii_main" retrace = "shieldprompt.cli:retrace_main" [project.urls] -Homepage = "https://github.com/shieldprompt/shieldprompt" -Documentation = "https://github.com/shieldprompt/shieldprompt#readme" +Homepage = "https://github.com/modelpath-dev/ShieldPrompt" +Documentation = "https://github.com/modelpath-dev/ShieldPrompt/blob/main/SETUP.md" +Issues = "https://github.com/modelpath-dev/ShieldPrompt/issues" [tool.hatch.build.targets.wheel] packages = ["src/shieldprompt"] diff --git a/src/shieldprompt/__init__.py b/src/shieldprompt/__init__.py index df5aabe..643962e 100644 --- a/src/shieldprompt/__init__.py +++ b/src/shieldprompt/__init__.py @@ -8,7 +8,7 @@ from .shield import Shield from .vault import Vault, get_vault -__version__ = "0.1.4" +__version__ = "0.1.5" __all__ = [ "Shield", diff --git a/src/shieldprompt/detector/hybrid.py b/src/shieldprompt/detector/hybrid.py index f5c0794..82aa331 100644 --- a/src/shieldprompt/detector/hybrid.py +++ b/src/shieldprompt/detector/hybrid.py @@ -15,7 +15,7 @@ class HybridDetector: """Combines regex (fast, deterministic) and NER (contextual) detection. NER backend selection order: - 1. Presidio (presidio-analyzer + spaCy en_core_web_lg) — preferred + 1. Presidio (presidio-analyzer + spaCy en_core_web_sm) — preferred 2. HuggingFace transformer (dslim/bert-base-NER) — fallback 3. Regex only — if neither is installed """ @@ -81,7 +81,7 @@ def _init_ner_backend( except ImportError: logger.info( "No NER backend installed — regex-only detection. " - "Install with: pip install shieldprompt[presidio] " + "Reinstall shieldprompt (Presidio is a core dependency) " "or: pip install shieldprompt[ner]" ) return None diff --git a/tests/test_decorator.py b/tests/test_decorator.py index ef8437a..451498d 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -25,9 +25,7 @@ async def echo_async(prompt: str) -> str: assert "bob@test.com" not in prompt return f"Got: {prompt}" - result = asyncio.get_event_loop().run_until_complete( - echo_async("Send to bob@test.com") - ) + result = asyncio.run(echo_async("Send to bob@test.com")) assert "bob@test.com" in result diff --git a/vault.json b/vault.json deleted file mode 100644 index 3e20188..0000000 --- a/vault.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "[LOCATION_1]": "Ancient Near East", - "[LOCATION_2]": "Iraq", - "[LOCATION_3]": "Uruk" -} \ No newline at end of file