Overview
This is a blank-slate feature request for the virustotal integration. No files have been added yet - the contributor is expected to build everything from scratch across two files: the API client layer in vt.py and the CLI command layer in virustotal.py.
The sections below describe exactly what needs to be built and the project's compliance requirements that must be met before requesting a review.
File structure
This project splits every feature domain into two files. Follow the same pattern:
| File |
Responsibility |
src/valkyrie_tools/vt.py |
VirusTotalClient dataclass, _get_api_key helper, VT_NO_API_KEY_MESSAGE constant. No Click imports. |
src/valkyrie_tools/virustotal.py |
Click CLI commands only. Imports from vt.py. |
Tests follow the same split:
| File |
Responsibility |
tests/test_vt.py |
Unit tests for VirusTotalClient and helpers (mocked requests) |
tests/test_virustotal.py |
CLI tests via CliRunner, inheriting BaseCommandTest |
Register the entry point in pyproject.toml:
virustotal = "valkyrie_tools.virustotal:cli"
What to build
Commands
Each command accepts a -j / --json flag. Without it, print a concise human-readable summary of the most relevant response fields (e.g. last_analysis_stats, reputation). With it, print the full JSON payload via json.dumps(result, indent=2) for piping.
The API key is stored in the package config and read at runtime:
valkyrie config set virustotalApiKey <your-key>
If the key is not set, the command must exit with a non-zero code and print VT_NO_API_KEY_MESSAGE (define it in vt.py).
Third-party client evaluation
Before writing anything, check whether a reputable VirusTotal Python client exists that is compatible with >=3.8,<4.0 and matches this project's synchronous requests-based HTTP pattern.
The official client vt-py must not be used. It requires aiohttp and aiofiles (async-only design) which is incompatible with every other HTTP call in this project. If you find another library that fits, document your reasoning in this issue.
If no suitable third-party library exists (the expected outcome), implement the following internal wrapper in src/valkyrie_tools/vt.py:
@dataclass
class VirusTotalClient:
api_key: str
base_url: str = "https://www.virustotal.com"
api_version: str = "v3"
base_api_url: str = field(init=False)
def __post_init__(self) -> None:
self.base_api_url = f"{self.base_url}/api/{self.api_version}"
def _build_url(self, path: str) -> str:
return f"{self.base_api_url}/{path.lstrip('/')}"
# Methods to implement (names flexible):
# scan_url(url: str) -> Dict[str, Any]
# get_domain(domain: str) -> Dict[str, Any]
# get_ip(ip: str) -> Dict[str, Any]
# get_file(hash_value: str) -> Dict[str, Any]
# scan_file(path: str) -> Dict[str, Any]
Each method should use requests (already a project dependency) with headers={"x-apikey": self.api_key} and call response.raise_for_status() before returning response.json().
Implementation checklist
Work through these in order. Every item must be complete before requesting a review.
src/valkyrie_tools/vt.py - API layer
src/valkyrie_tools/virustotal.py - CLI layer
Tests
Final
Compliance requirements
This project has strict quality gates that CI will reject if not met. Read DEVELOPMENT.md before writing any code. The key requirements are summarised below.
Formatting and linting
black (80-char line length), isort, flake8, and darglint all run automatically. Run before every commit:
poetry run nox -s pre-commit
Or manually:
poetry run black src tests
poetry run isort src tests
poetry run flake8 src tests
Key rules:
- Maximum line length: 80 characters
- Import order: black-compatible (isort
profile = black)
- Cyclomatic complexity: max 10
- No unused imports, no bare
except, no print (use click.echo)
See DEVELOPMENT.md - Code Style and DEVELOPMENT.md - Linting.
Docstrings
Every public function, class, and module must have a Google-style docstring. darglint enforces that Args:, Returns:, and Raises: sections are present whenever the function has arguments, a return value, or raises exceptions.
Example of a compliant docstring:
def get_domain(self, domain: str) -> Dict[str, Any]:
"""Retrieve a domain report from VirusTotal.
Args:
domain: The domain name to look up (e.g. ``"example.com"``).
Returns:
Parsed JSON response from the VirusTotal API.
Raises:
requests.HTTPError: If the API returns a 4xx or 5xx status.
"""
See DEVELOPMENT.md - Docstring Standards.
Type annotations
mypy runs in strict mode. Every function argument and return type must be fully annotated. requests stubs (types-requests) are already a project dependency. Do not use # type: ignore without a comment explaining why.
Run manually:
poetry run mypy src tests docs/conf.py
See DEVELOPMENT.md - Type Annotations.
Test coverage
Coverage must be 100%. Every branch of every new function must be exercised. Never make real network calls in tests - patch VirusTotalClient methods and requests calls with unittest.mock.patch.
After writing tests:
poetry run coverage run -m pytest
poetry run coverage report
Any line shown as missing in the report must be covered before the PR can merge.
See DEVELOPMENT.md - Tests.
Auto-generated documentation
Add the virustotal CLI and API entries to docs/reference.rst, then verify the Sphinx build produces zero warnings:
poetry run nox -s docs-build
If you add or rename any public function or class, update the docstring and confirm the build still passes.
See DEVELOPMENT.md - Session reference (docs-build).
Full quality gate
Run the complete suite before pushing:
This runs pre-commit, safety, tests, xdoctest, and docs-build in sequence. All sessions must be green.
Version
Bump the version from 1.3.2 to 1.4.0 in pyproject.toml (minor bump for a new backward-compatible feature).
Getting started
# 1. Fork or clone the repo
gh repo fork xransum/valkyrie-tools --clone
# 2. Install dependencies
poetry install
# 3. Check out this branch
git checkout feature/virustotal
# 4. Install pre-commit hooks (run once)
poetry run pre-commit install
# 5. Iterate - run the relevant session after each change
poetry run nox -s tests # after logic changes
poetry run nox -s pre-commit # before committing
poetry run mypy src tests docs/conf.py # after type annotation changes
# 6. Full gate before pushing
poetry run nox
Overview
This is a blank-slate feature request for the
virustotalintegration. No files have been added yet - the contributor is expected to build everything from scratch across two files: the API client layer invt.pyand the CLI command layer invirustotal.py.The sections below describe exactly what needs to be built and the project's compliance requirements that must be met before requesting a review.
File structure
This project splits every feature domain into two files. Follow the same pattern:
src/valkyrie_tools/vt.pyVirusTotalClientdataclass,_get_api_keyhelper,VT_NO_API_KEY_MESSAGEconstant. No Click imports.src/valkyrie_tools/virustotal.pyvt.py.Tests follow the same split:
tests/test_vt.pyVirusTotalClientand helpers (mockedrequests)tests/test_virustotal.pyCliRunner, inheritingBaseCommandTestRegister the entry point in
pyproject.toml:What to build
Commands
virustotal url "<url>"virustotal domain "<domain>"virustotal ip "<ip>"virustotal hash "<hash>"virustotal file <path>Each command accepts a
-j / --jsonflag. Without it, print a concise human-readable summary of the most relevant response fields (e.g.last_analysis_stats,reputation). With it, print the full JSON payload viajson.dumps(result, indent=2)for piping.The API key is stored in the package config and read at runtime:
If the key is not set, the command must exit with a non-zero code and print
VT_NO_API_KEY_MESSAGE(define it invt.py).Third-party client evaluation
Before writing anything, check whether a reputable VirusTotal Python client exists that is compatible with
>=3.8,<4.0and matches this project's synchronousrequests-based HTTP pattern.The official client
vt-pymust not be used. It requiresaiohttpandaiofiles(async-only design) which is incompatible with every other HTTP call in this project. If you find another library that fits, document your reasoning in this issue.If no suitable third-party library exists (the expected outcome), implement the following internal wrapper in
src/valkyrie_tools/vt.py:Each method should use
requests(already a project dependency) withheaders={"x-apikey": self.api_key}and callresponse.raise_for_status()before returningresponse.json().Implementation checklist
Work through these in order. Every item must be complete before requesting a review.
src/valkyrie_tools/vt.py- API layerVT_NO_API_KEY_MESSAGEconstant_get_api_keyhelperVirusTotalClient.__post_init__and_build_urlVirusTotalClient.scan_urlVirusTotalClient.get_domainVirusTotalClient.get_ipVirusTotalClient.get_fileVirusTotalClient.scan_filesrc/valkyrie_tools/virustotal.py- CLI layer_print_resulthelper (summary view +--jsonpath)scan_url_cmdCLI sub-command bodyget_domain_cmdCLI sub-command bodyget_ip_cmdCLI sub-command bodyget_hash_cmdCLI sub-command bodyscan_file_cmdCLI sub-command bodyTests
tests/test_vt.py(mockrequests, cover allVirusTotalClientmethods and helpers)tests/test_virustotal.py(useCliRunner, inheritBaseCommandTest)Final
virustotal = "valkyrie_tools.virustotal:cli"inpyproject.tomlvirustotalCLI and API entries todocs/reference.rstCompliance requirements
This project has strict quality gates that CI will reject if not met. Read
DEVELOPMENT.mdbefore writing any code. The key requirements are summarised below.Formatting and linting
black (80-char line length), isort, flake8, and darglint all run automatically. Run before every commit:
Or manually:
Key rules:
profile = black)except, noprint(useclick.echo)See DEVELOPMENT.md - Code Style and DEVELOPMENT.md - Linting.
Docstrings
Every public function, class, and module must have a Google-style docstring. darglint enforces that
Args:,Returns:, andRaises:sections are present whenever the function has arguments, a return value, or raises exceptions.Example of a compliant docstring:
See DEVELOPMENT.md - Docstring Standards.
Type annotations
mypy runs in strict mode. Every function argument and return type must be fully annotated.
requestsstubs (types-requests) are already a project dependency. Do not use# type: ignorewithout a comment explaining why.Run manually:
See DEVELOPMENT.md - Type Annotations.
Test coverage
Coverage must be 100%. Every branch of every new function must be exercised. Never make real network calls in tests - patch
VirusTotalClientmethods andrequestscalls withunittest.mock.patch.After writing tests:
Any line shown as missing in the report must be covered before the PR can merge.
See DEVELOPMENT.md - Tests.
Auto-generated documentation
Add the
virustotalCLI and API entries todocs/reference.rst, then verify the Sphinx build produces zero warnings:If you add or rename any public function or class, update the docstring and confirm the build still passes.
See DEVELOPMENT.md - Session reference (docs-build).
Full quality gate
Run the complete suite before pushing:
This runs
pre-commit,safety,tests,xdoctest, anddocs-buildin sequence. All sessions must be green.Version
Bump the version from
1.3.2to1.4.0inpyproject.toml(minor bump for a new backward-compatible feature).Getting started