Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Run a model in a few lines.

Python API:
```python
from moderators.auto_model import AutoModerator
from moderators import AutoModerator

# Load from the Hugging Face Hub (e.g., NSFW image classifier)
moderator = AutoModerator.from_pretrained("viddexa/nsfw-mini")
Expand Down Expand Up @@ -101,7 +101,7 @@ JSON shape (CLI output):
Tip (Python):
```python
from dataclasses import asdict
from moderators.auto_model import AutoModerator
from moderators import AutoModerator

moderator = AutoModerator.from_pretrained("viddexa/nsfw-mini")
result = moderator("/path/to/image.jpg")
Expand Down
16 changes: 8 additions & 8 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ uv add moderators

## Quickstart (Image, Python)
```python
from moderators.auto_model import AutoModerator
from moderators import AutoModerator

# NSFW image classification model from the Hub
moderator = AutoModerator.from_pretrained("viddexa/nsfw-mini")
moderator = AutoModerator.from_pretrained("viddexa/nsfw-detector-mini")

# Run on a local image path
result = moderator("/path/to/image.jpg")
Expand All @@ -30,7 +30,7 @@ print(result)

## Quickstart (Image, CLI)
```bash
moderators viddexa/nsfw-mini /path/to/image.jpg
moderators viddexa/nsfw-detector-mini /path/to/image.jpg
```

Tip: Add `--local-files-only` to force offline usage if the files are already cached.
Expand All @@ -40,10 +40,10 @@ Process a directory of images and print the top result per file.

```python
from pathlib import Path
from moderators.auto_model import AutoModerator
from moderators import AutoModerator

images_dir = Path("/path/to/images")
model = AutoModerator.from_pretrained("viddexa/nsfw-mini")
model = AutoModerator.from_pretrained("viddexa/nsfw-detector-mini")

for img_path in images_dir.glob("**/*"):
if img_path.suffix.lower() in {".jpg", ".jpeg", ".png", ".webp", ".bmp", ".gif", ".avif"}:
Expand All @@ -56,7 +56,7 @@ You can also load a text classifier.

Python:
```python
from moderators.auto_model import AutoModerator
from moderators import AutoModerator

text_model = AutoModerator.from_pretrained("distilbert/distilbert-base-uncased-finetuned-sst-2-english")
print(text_model("I love this!"))
Expand All @@ -78,13 +78,13 @@ python examples/benchmarks.py <model_id> <image_path> [--warmup N] [--repeats N]
Examples:
```bash
# Default backend (auto-detected)
python examples/benchmarks.py viddexa/nsfw-mini /path/to/image.jpg --warmup 3 --repeats 20
python examples/benchmarks.py viddexa/nsfw-detector-mini /path/to/image.jpg --warmup 3 --repeats 20

```

Expected output (sample):
```
Model: viddexa/nsfw-mini
Model: viddexa/nsfw-detector-mini
Backend: auto
Runs: 20, avg: 12.34 ms, p50: 11.80 ms, p90: 14.10 ms
```
Expand Down
2 changes: 1 addition & 1 deletion examples/benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import statistics
import time

from moderators.auto_model import AutoModerator
from moderators import AutoModerator


def summarize(times: list[float]) -> dict[str, float]:
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ packages = ["src/moderators"]

[tool.ruff]
line-length = 120
target-version = "py310"
target-version = "py39"

[tool.ruff.lint]
extend-select = ["I", "D", "UP"]
extend-select = ["F", "I", "D", "UP", "RUF", "FA"]
ignore = [
"D100", # Missing docstring in public module
"D104", # Missing docstring in public package
Expand All @@ -58,6 +58,9 @@ ignore = [
"D406", # Section name should end with a newline
"D407", # Missing dashed underline after section
"D413", # Missing blank line after last section
"RUF001", # Ambiguous Unicode characters in strings
"RUF002", # Ambiguous Unicode characters in docstrings
"RUF012", # Mutable default values in class attributes
]

[tool.ruff.lint.per-file-ignores]
Expand Down
6 changes: 5 additions & 1 deletion src/moderators/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
__version__ = "0.1.0"
__version__ = "0.1.1"

from .auto_model import AutoModerator

__all__ = ["AutoModerator"]
2 changes: 1 addition & 1 deletion src/moderators/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
from dataclasses import asdict, is_dataclass

from moderators.auto_model import AutoModerator
from moderators import AutoModerator


def _to_jsonable(obj):
Expand Down
2 changes: 1 addition & 1 deletion src/moderators/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

__all__ = [
"auto_install",
"ensure_transformers",
"ensure_dl_framework",
"ensure_pillow_for_task",
"ensure_transformers",
"preprocess_image_input",
]
6 changes: 3 additions & 3 deletions src/moderators/utils/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def auto_install(packages: list[str]) -> bool:
def ensure_transformers(install_fn: Callable[[list[str]], bool]):
"""Ensure 'transformers' is importable; optionally auto-install and retry."""
try:
import transformers as _transformers # noqa: F401
import transformers as _transformers

return _transformers
except Exception:
Expand All @@ -48,7 +48,7 @@ def ensure_dl_framework(install_fn: Callable[[list[str]], bool]) -> str:
Tries to auto-install torch first.
"""
try:
import torch # noqa: F401
import torch

return "pt"
except Exception:
Expand Down Expand Up @@ -81,7 +81,7 @@ def ensure_pillow_for_task(task: str, install_fn: Callable[[list[str]], bool]) -
if "image" not in str(task).lower():
return
try:
import PIL # noqa: F401
import PIL
except Exception:
if not install_fn(["Pillow"]):
raise ImportError("This image task requires Pillow. Install with: uv pip install Pillow")
Expand Down
4 changes: 2 additions & 2 deletions src/moderators/utils/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def _process(obj: Any):
w, h = img.size
if w < min_side or h < min_side:
scale = max(min_side / w, min_side / h)
new_w = int(round(w * scale))
new_h = int(round(h * scale))
new_w = round(w * scale)
new_h = round(h * scale)
resample = getattr(getattr(Image, "Resampling", Image), "BILINEAR")
img = img.resize((new_w, new_h), resample)
except Exception:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_auto_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from moderators.auto_model import AutoModerator
from moderators import AutoModerator
from moderators.integrations.base import PredictionResult
from moderators.integrations.transformers_moderator import TransformersModerator

Expand Down
18 changes: 9 additions & 9 deletions tests/test_push_to_hub.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from pathlib import Path

from moderators.auto_model import AutoModerator
from moderators import AutoModerator


class _FakeTempDir:
Expand All @@ -14,7 +14,7 @@ def __enter__(self):
return self.name

def __exit__(self, exc_type, exc, tb):
# Klasörü bilerek silmiyoruz; test sonrasında pytest tmp cleanup zaten yapar.
# Intentionally not deleting folder; pytest tmp cleanup handles it after tests.
return False


Expand All @@ -25,7 +25,7 @@ def write_config(tmp_path: Path, data: dict) -> Path:


def test_push_to_hub_offline(tmp_path, monkeypatch, fake_transformers):
# Ağ ve ağır bağımlılıkları devre dışı bırak
# Disable network and heavy dependencies
monkeypatch.setenv("MODERATORS_DISABLE_AUTO_INSTALL", "1")
monkeypatch.setattr(
"moderators.integrations.transformers_moderator.ensure_dl_framework",
Expand Down Expand Up @@ -56,35 +56,35 @@ def upload_folder(self, repo_id, repo_type, folder_path, commit_message=None, to
}
return {"ok": True}

# Doğru namespace: hub_mixin içindeki HfApi sembolünü patch'le
# Correct namespace: patch the HfApi symbol in hub_mixin
monkeypatch.setattr(
"huggingface_hub.hub_mixin.HfApi",
lambda *a, **k: FakeApi(),
raising=True,
)

# Geçici klasörün silinmesini engelle: SoftTemporaryDirectory'yi patch'le
# Prevent deletion of temp folder: patch SoftTemporaryDirectory
monkeypatch.setattr(
"huggingface_hub.hub_mixin.SoftTemporaryDirectory",
lambda *a, **k: _FakeTempDir(prefix="moderators_push_"),
raising=True,
)

# Yerel bir TransformersModerator konfigürasyonu ile yükle
# Load with a local TransformersModerator configuration
model_dir = write_config(tmp_path, {"architecture": "TransformersModerator", "task": "text-classification"})
mod = AutoModerator.from_pretrained(str(model_dir))

# push_to_hub çağrısı (ağ yok, FakeApi çalışacak)
# push_to_hub call (no network, FakeApi will run)
repo_id = "user/repo-for-tests"
mod.push_to_hub(repo_id, commit_message="test commit", token="fake-token")

# upload_folder çağrıldı mı?
# Was upload_folder called?
assert "upload_folder" in calls
up = calls["upload_folder"]
folder_path = Path(up["folder_path"])
assert folder_path.exists()

# Kaydedilen config.json doğrula
# Verify saved config.json
cfg = json.loads((folder_path / "config.json").read_text(encoding="utf-8"))
assert cfg.get("architecture") == "TransformersModerator"
assert cfg.get("task") == "text-classification"