diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..276d33f --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,18 @@ +name: Linting & Code Quality + +on: + push: + branches: [v0.2, main] + pull_request: + branches: [v0.2, main] + +jobs: + lint: + name: pre-commit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - uses: pre-commit/action@v3.0.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7c2f12e --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,55 @@ +default_language_version: + python: python3.11 + +exclude: "tests/" + +fail_fast: true + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + args: ["--maxkb=500"] + - id: check-merge-conflict + + - repo: https://github.com/tox-dev/pyproject-fmt + rev: v2.21.1 + hooks: + - id: pyproject-fmt + name: Format and validate pyproject.toml + files: ^pyproject\.toml$ + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.11 + hooks: + - id: ruff-check + args: [--fix] + - id: ruff-format + + - repo: https://github.com/PyCQA/bandit + rev: 1.9.4 + hooks: + - id: bandit + args: ["-c", "pyproject.toml"] + additional_dependencies: ["bandit[toml]"] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.20.1 + hooks: + - id: mypy + name: Static type checking + args: ["--config-file=pyproject.toml"] + additional_dependencies: [types-requests, "pydantic>=2.4"] + + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.11.7 + hooks: + - id: uv-lock + files: ^pyproject\.toml$ + - id: uv-export + args: ["--frozen", "--no-dev", "--no-hashes", "-o", "requirements.txt"] + files: ^pyproject\.toml$ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a48e76..6ed4e2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,8 +13,19 @@ Thank you for your interest in contributing to the OWASP AIBOM Generator! This p 3. **Set up your environment**: ```bash - # Local Python setup - pip install -r requirements.txt + pip install -e ".[dev]" + ``` + +4. **Install pre-commit hooks**: + + ```bash + pre-commit install + ``` + + Hooks run automatically on every `git commit`. To run them manually against all files: + + ```bash + pre-commit run --all-files ``` ## Development Workflow @@ -66,9 +77,24 @@ type(scope): description ## Code Standards +### Pre-commit Hooks + +This project uses [pre-commit](https://pre-commit.com) to enforce code quality automatically. The following hooks run on every commit: + +| Hook | Purpose | +|------|---------| +| `trailing-whitespace`, `end-of-file-fixer`, `check-yaml`, `check-merge-conflict` | File hygiene | +| `pyproject-fmt` | Normalizes and validates `pyproject.toml` | +| `ruff` (check + format) | Linting and formatting (replaces flake8, isort, autopep8) | +| `bandit` | Security vulnerability scanning | +| `mypy` | Static type checking | +| `uv-lock` + `uv-export` | Keeps `uv.lock` and `requirements.txt` in sync with `pyproject.toml` | + +If a hook fails, fix the reported issues and re-commit. + ### Python Style -- **Python 3.8+** compatibility required +- **Python 3.11+** required - Follow existing patterns in the codebase - Use type hints for function signatures diff --git a/README.md b/README.md index c9fbf5d..bd1ed2b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # 🤖 OWASP GenAI Security Project - AIBOM Generator -This is the official GitHub repository for the **OWASP AIBOM Generator** — an open-source tool for generating **AI Bills of Materials (AIBOMs)** in [CycloneDX](https://cyclonedx.org) format. +This is the official GitHub repository for the **OWASP AIBOM Generator** — an open-source tool for generating **AI Bills of Materials (AIBOMs)** in [CycloneDX](https://cyclonedx.org) format. The tool is also listed in the official **[CycloneDX Tool Center](https://cyclonedx.org/tool-center/)**. -🚀 **Try the tool live:** -👉 https://owasp-genai-aibom.org -🔖 Bookmark and share: https://owasp-genai-aibom.org +🚀 **Try the tool live:** +👉 https://owasp-genai-aibom.org +🔖 Bookmark and share: https://owasp-genai-aibom.org 🌐 OWASP AIBOM Initiative: [genai.owasp.org](https://genai.owasp.org/) @@ -16,10 +16,10 @@ The tool is also listed in the official **[CycloneDX Tool Center](https://cyclon ## 📦 What It Does -- Extracts metadata from models hosted on Hugging Face 🤗 -- Generates an **AIBOM** (AI Bill of Materials) in CycloneDX 1.6 JSON format -- Calculates **AIBOM completeness scoring** with recommendations -- Supports metadata extraction from model cards, configurations, and repository files +- Extracts metadata from models hosted on Hugging Face 🤗 +- Generates an **AIBOM** (AI Bill of Materials) in CycloneDX 1.6 JSON format +- Calculates **AIBOM completeness scoring** with recommendations +- Supports metadata extraction from model cards, configurations, and repository files > **Built on [CycloneDX Python Library](https://github.com/CycloneDX/cyclonedx-python-lib)** — BOM generation, schema validation, and SPDX license handling are all powered by the official `cyclonedx-python-lib`, ensuring spec-compliant output without manual JSON construction. @@ -27,10 +27,10 @@ The tool is also listed in the official **[CycloneDX Tool Center](https://cyclon ## 🛠 Features -- Human-readable AIBOM viewer -- JSON download -- Completeness scoring & improvement tips -- API endpoints for automation +- Human-readable AIBOM viewer +- JSON download +- Completeness scoring & improvement tips +- API endpoints for automation - Standards-aligned generation (CycloneDX 1.6, compatible with SPDX AI Profile) --- @@ -93,11 +93,26 @@ python3 -m src.cli google-bert/bert-base-uncased \ We welcome contributions and feedback. -➡ **Log an issue:** +➡ **Log an issue:** https://github.com/GenAI-Security-Project/aibom-generator/issues --- +## 🤝 Contributing + +We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide. + +Quick setup for contributors: + +```bash +pip install -e ".[dev]" +pre-commit install +``` + +Pre-commit hooks enforce code quality (ruff, bandit, mypy, pyproject-fmt) automatically on every commit. CI also runs them on all PRs. + +--- + ## 📄 License This project is open-source and available under the [Apache 2.0 License](LICENSE). diff --git a/pyproject.toml b/pyproject.toml index e9fba77..5cdf536 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,79 +1,108 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = [ "setuptools>=61", "wheel" ] [project] name = "owasp-aibom-generator" version = "1.0.2" description = "A comprehensive AI Bill of Materials (AIBOM) generation tool for Hugging Face models." -authors = [ - { name = "OWASP GenAI Security Project", email = "genai-security@owasp.org" } -] readme = "README.md" -requires-python = ">=3.11" license = { text = "Apache-2.0" } +authors = [ { name = "OWASP GenAI Security Project", email = "genai-security@owasp.org" } ] +requires-python = ">=3.11" classifiers = [ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Topic :: Security", - "Topic :: Scientific/Engineering :: Artificial Intelligence" + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Security", ] dependencies = [ - "beautifulsoup4>=4.11.0", - "cyclonedx-python-lib[json-validation]>=11.7.0", - "datasets>=2.0.0", - "fastapi>=0.104.0", - "flask>=2.3.0", - "gunicorn>=21.2.0", - "httpx>=0.25.0", - "huggingface_hub>=0.19.0", - "jinja2>=3.0.0", - "packageurl-python>=0.11.1", - "pydantic>=2.4.0", - "python-multipart", - "PyYAML>=6.0.1", - "requests>=2.31.0", - "sentencepiece>=0.1.99", - "torch>=2.0.0", - "transformers>=4.36.0", - "uvicorn>=0.24.0", - "safetensors>=0.4.0", + "beautifulsoup4>=4.11", + "datasets>=2", + "fastapi>=0.104", + "flask>=2.3", + "gunicorn>=21.2", + "httpx>=0.25", + "huggingface-hub>=0.19", + "jinja2>=3", + "jsonschema>=4.17", + "license-expression>=30.4.4", + "packageurl-python>=0.11.1", + "pydantic>=2.4", + "python-multipart", + "pyyaml>=6.0.1", + "requests>=2.31", + "safetensors>=0.4.0", + "sentencepiece>=0.1.99", + "torch>=2", + "transformers>=4.36", + "uvicorn>=0.24", +] +optional-dependencies.dev = [ + "gguf>=0.6", + "pre-commit>=3", + "pytest>=7", + "pytest-cov>=4", + "pytest-mock>=3.10", ] +scripts.aibom = "src.cli:main" -[project.optional-dependencies] +[dependency-groups] dev = [ - "pytest>=7.0.0", - "pytest-cov>=4.0.0", - "pytest-mock>=3.10.0", - "ruff", - "gguf>=0.6.0" + "gguf>=0.6", ] -[project.scripts] -aibom = "src.cli:main" +[tool.setuptools] +packages.find.where = [ "." ] +packages.find.include = [ "src*" ] +packages.find.namespaces = false -[build-system] -requires = ["setuptools>=61.0", "wheel"] -build-backend = "setuptools.build_meta" +[tool.ruff] +target-version = "py311" +line-length = 120 +lint.select = [ + "B", # flake8-bugbear + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort + "T10", # flake8-debugger + "UP", # pyupgrade + "W", # pycodestyle warnings +] -[tool.setuptools.packages.find] -where = ["."] -include = ["src*"] -namespaces = false +[tool.bandit] +exclude_dirs = [ "tests" ] +skips = [ "B101" ] -[tool.pytest.ini_options] -minversion = "6.0" -addopts = "-ra -q --cov=src" -testpaths = [ - "tests", -] -pythonpath = [ - "." +[tool.mypy] +python_version = "3.11" +ignore_missing_imports = true +install_types = true +non_interactive = true +check_untyped_defs = true +warn_unreachable = true +warn_unused_ignores = true +exclude = "tests/" +plugins = [ "pydantic.mypy" ] + +[[tool.mypy.overrides]] +ignore_errors = true +module = [ + "src.models.extractor", + "src.models.gguf_metadata", + "src.models.registry", + "src.models.service", ] -[dependency-groups] -dev = [ - "pytest>=7.0.0", - "pytest-cov>=4.0.0", - "pytest-mock>=3.10.0", - "ruff", - "gguf>=0.6.0", +[tool.pytest] +ini_options.minversion = "6.0" +ini_options.addopts = "-ra -q --cov=src" +ini_options.testpaths = [ + "tests", ] +ini_options.pythonpath = [ "." ] diff --git a/requirements.txt b/requirements.txt index 9c195ee..f6914f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,26 +1,293 @@ -huggingface_hub>=0.19.0 -transformers>=4.36.0 -torch>=2.0.0 -fastapi>=0.104.0 -uvicorn>=0.24.0 -pydantic>=2.4.0 -requests>=2.31.0 -PyYAML>=6.0.1 -flask>=2.3.0 -gunicorn>=21.2.0 -cyclonedx-python-lib[json-validation]>=11.7.0 -httpx>=0.25.0 -packageurl-python>=0.17.6 -python-multipart -jinja2>=3.0.0 -datasets>=2.0.0 -beautifulsoup4>=4.11.0 -sentencepiece>=0.1.99 -safetensors>=0.4.0 - -# Test dependencies -pytest>=7.0.0 -pytest-mock>=3.10.0 -pytest-cov>=4.0.0 -ruff -gguf>=0.6.0 +# This file was autogenerated by uv via the following command: +# uv export --frozen --no-dev --no-hashes -o requirements.txt +-e . +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.3 + # via fsspec +aiosignal==1.4.0 + # via aiohttp +annotated-doc==0.0.4 + # via + # fastapi + # typer +annotated-types==0.7.0 + # via pydantic +anyio==4.12.1 + # via + # httpx + # starlette +attrs==25.4.0 + # via + # aiohttp + # jsonschema + # referencing +beautifulsoup4==4.14.3 + # via owasp-aibom-generator +blinker==1.9.0 + # via flask +boolean-py==5.0 + # via license-expression +certifi==2026.1.4 + # via + # httpcore + # httpx + # requests +charset-normalizer==3.4.4 + # via requests +click==8.3.1 + # via + # flask + # typer + # uvicorn +colorama==0.4.6 ; sys_platform == 'win32' + # via + # click + # tqdm +cuda-bindings==12.9.4 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +cuda-pathfinder==1.3.5 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via cuda-bindings +datasets==4.5.0 + # via owasp-aibom-generator +dill==0.4.0 + # via + # datasets + # multiprocess +fastapi==0.132.0 + # via owasp-aibom-generator +filelock==3.24.3 + # via + # datasets + # huggingface-hub + # torch +flask==3.1.3 + # via owasp-aibom-generator +frozenlist==1.8.0 + # via + # aiohttp + # aiosignal +fsspec==2025.10.0 + # via + # datasets + # huggingface-hub + # torch +gunicorn==25.1.0 + # via owasp-aibom-generator +h11==0.16.0 + # via + # httpcore + # uvicorn +hf-xet==1.3.0 ; platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' + # via huggingface-hub +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via + # datasets + # huggingface-hub + # owasp-aibom-generator +huggingface-hub==1.4.1 + # via + # datasets + # owasp-aibom-generator + # tokenizers + # transformers +idna==3.11 + # via + # anyio + # httpx + # requests + # yarl +itsdangerous==2.2.0 + # via flask +jinja2==3.1.6 + # via + # flask + # owasp-aibom-generator + # torch +jsonschema==4.26.0 + # via owasp-aibom-generator +jsonschema-specifications==2025.9.1 + # via jsonschema +license-expression==30.4.4 + # via owasp-aibom-generator +markdown-it-py==4.0.0 + # via rich +markupsafe==3.0.3 + # via + # flask + # jinja2 + # werkzeug +mdurl==0.1.2 + # via markdown-it-py +mpmath==1.3.0 + # via sympy +multidict==6.7.1 + # via + # aiohttp + # yarl +multiprocess==0.70.18 + # via datasets +networkx==3.6.1 + # via torch +numpy==2.4.2 + # via + # datasets + # pandas + # transformers +nvidia-cublas-cu12==12.8.4.1 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via + # nvidia-cudnn-cu12 + # nvidia-cusolver-cu12 + # torch +nvidia-cuda-cupti-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cuda-nvrtc-cu12==12.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cuda-runtime-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cudnn-cu12==9.10.2.21 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cufft-cu12==11.3.3.83 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cufile-cu12==1.13.1.3 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-curand-cu12==10.3.9.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cusolver-cu12==11.7.3.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cusparse-cu12==12.5.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via + # nvidia-cusolver-cu12 + # torch +nvidia-cusparselt-cu12==0.7.1 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-nccl-cu12==2.27.5 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-nvjitlink-cu12==12.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via + # nvidia-cufft-cu12 + # nvidia-cusolver-cu12 + # nvidia-cusparse-cu12 + # torch +nvidia-nvshmem-cu12==3.4.5 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-nvtx-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +packageurl-python==0.17.6 + # via owasp-aibom-generator +packaging==26.0 + # via + # datasets + # gunicorn + # huggingface-hub + # transformers +pandas==3.0.1 + # via datasets +propcache==0.4.1 + # via + # aiohttp + # yarl +pyarrow==23.0.1 + # via datasets +pydantic==2.12.5 + # via + # fastapi + # owasp-aibom-generator +pydantic-core==2.41.5 + # via pydantic +pygments==2.19.2 + # via rich +python-dateutil==2.9.0.post0 + # via pandas +python-multipart==0.0.22 + # via owasp-aibom-generator +pyyaml==6.0.3 + # via + # datasets + # huggingface-hub + # owasp-aibom-generator + # transformers +referencing==0.37.0 + # via + # jsonschema + # jsonschema-specifications +regex==2026.2.19 + # via transformers +requests==2.32.5 + # via + # datasets + # owasp-aibom-generator +rich==14.3.3 + # via typer +rpds-py==0.30.0 + # via + # jsonschema + # referencing +safetensors==0.7.0 + # via transformers +sentencepiece==0.2.1 + # via owasp-aibom-generator +setuptools==82.0.0 ; python_full_version >= '3.12' + # via torch +shellingham==1.5.4 + # via + # huggingface-hub + # typer +six==1.17.0 + # via python-dateutil +soupsieve==2.8.3 + # via beautifulsoup4 +starlette==0.52.1 + # via fastapi +sympy==1.14.0 + # via torch +tokenizers==0.22.2 + # via transformers +torch==2.10.0 + # via owasp-aibom-generator +tqdm==4.67.3 + # via + # datasets + # huggingface-hub + # transformers +transformers==5.2.0 + # via owasp-aibom-generator +triton==3.6.0 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +typer==0.24.1 + # via typer-slim +typer-slim==0.24.0 + # via + # huggingface-hub + # transformers +typing-extensions==4.15.0 + # via + # aiosignal + # anyio + # beautifulsoup4 + # fastapi + # huggingface-hub + # pydantic + # pydantic-core + # referencing + # starlette + # torch + # typing-inspection +typing-inspection==0.4.2 + # via + # fastapi + # pydantic +tzdata==2025.3 ; sys_platform == 'emscripten' or sys_platform == 'win32' + # via pandas +urllib3==2.6.3 + # via requests +uvicorn==0.41.0 + # via owasp-aibom-generator +werkzeug==3.1.6 + # via flask +xxhash==3.6.0 + # via datasets +yarl==1.22.0 + # via aiohttp diff --git a/uv.lock b/uv.lock index 29175a8..227c2c7 100644 --- a/uv.lock +++ b/uv.lock @@ -230,6 +230,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -508,6 +517,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + [[package]] name = "fastapi" version = "0.132.0" @@ -791,6 +809,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/ae/2f6d96b4e6c5478d87d606a1934b5d436c4a2bce6bb7c6fdece891c128e3/huggingface_hub-1.4.1-py3-none-any.whl", hash = "sha256:9931d075fb7a79af5abc487106414ec5fba2c0ae86104c0c62fd6cae38873d18", size = 553326, upload-time = "2026-02-06T09:20:00.728Z" }, ] +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -1162,6 +1189,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "numpy" version = "2.4.2" @@ -1404,10 +1440,10 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "gguf" }, + { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-mock" }, - { name = "ruff" }, ] [package.dev-dependencies] @@ -1432,6 +1468,7 @@ requires-dist = [ { name = "huggingface-hub", specifier = ">=0.19.0" }, { name = "jinja2", specifier = ">=3.0.0" }, { name = "packageurl-python", specifier = ">=0.11.1" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.0.0" }, { name = "pydantic", specifier = ">=2.4.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0.0" }, @@ -1439,8 +1476,6 @@ requires-dist = [ { name = "python-multipart" }, { name = "pyyaml", specifier = ">=6.0.1" }, { name = "requests", specifier = ">=2.31.0" }, - { name = "ruff", marker = "extra == 'dev'" }, - { name = "safetensors", specifier = ">=0.4.0" }, { name = "sentencepiece", specifier = ">=0.1.99" }, { name = "torch", specifier = ">=2.0.0" }, { name = "transformers", specifier = ">=4.36.0" }, @@ -1535,6 +1570,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, ] +[[package]] +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -1544,6 +1588,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + [[package]] name = "propcache" version = "0.4.1" @@ -1868,6 +1928,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, +] + [[package]] name = "python-multipart" version = "0.0.22" @@ -2219,31 +2292,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] -[[package]] -name = "ruff" -version = "0.15.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, - { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, - { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, - { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, - { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, - { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, - { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, - { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, - { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, -] - [[package]] name = "safetensors" version = "0.7.0" @@ -2662,6 +2710,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, ] +[[package]] +name = "virtualenv" +version = "21.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742, upload-time = "2026-04-14T22:15:31.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232, upload-time = "2026-04-14T22:15:29.342Z" }, +] + [[package]] name = "webcolors" version = "25.10.0"