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
61 changes: 61 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Integration Tests

on:
workflow_dispatch:
push:
branches:
- master
pull_request:

jobs:
integration:
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true

- name: Start clamd container
run: |
docker run -d --name clamd -p 3310:3310 clamav/clamav:stable

- name: Wait for clamd
run: |
for i in $(seq 1 60); do
if bash -c '</dev/tcp/127.0.0.1/3310' 2>/dev/null; then
echo "clamd is reachable"
exit 0
fi
echo "waiting for clamd..."
sleep 5
done
echo "clamd did not become reachable in time"
docker logs clamd
exit 1

- name: Run integration tests
env:
PYVALVE_RUN_INTEGRATION: "1"
PYVALVE_CLAMD_HOST: "127.0.0.1"
PYVALVE_CLAMD_PORT: "3310"
run: |
make integration

- name: Print clamd logs on failure
if: failure()
run: docker logs clamd

- name: Stop clamd
if: always()
run: docker rm -f clamd
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ repos:
hooks:
- id: pylint
name: pylint
entry: pytest --pylint src/
entry: uv run --extra dev pytest --pylint src/pyvalve
language: system
pass_filenames: false
always_run: true
- id: mypy
name: mypy
entry: pytest --mypy src/
entry: uv run --extra dev pytest --mypy src/pyvalve
language: system
pass_filenames: false
always_run: true
- id: pytest
name: pytest
entry: pytest test/
entry: uv run --extra dev pytest test/
language: system
pass_filenames: false
always_run: true
49 changes: 49 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Makefile for pyvalve project with uv

.PHONY: help install dev test lint type format clean lock check integration build

help:
@echo "Common make targets:"
@echo " install - Install project with uv"
@echo " dev - Install project with dev dependencies"
@echo " lint - Run pre-commit hooks"
@echo " type - Run mypy type checks"
@echo " test - Run tests with pytest"
@echo " integration - Run clamd integration tests"
@echo " check - Lint, type check, and test"
@echo " format - Run formatters (via pre-commit)"
@echo " build - Build wheel and sdist artifacts"
@echo " lock - Update uv.lock"
@echo " clean - Remove caches and local venv"

check: lint type test

install:
uv pip install .

dev:
uv pip install .[dev]

test:
uv run --extra dev pytest

integration:
PYVALVE_RUN_INTEGRATION=1 uv run --extra dev pytest -m integration

lint:
uv run --extra dev pre-commit run --all-files

type:
uv run --extra dev mypy src/pyvalve

format:
uv run --extra dev pre-commit run --all-files

build:
uv build

lock:
uv lock

clean:
rm -rf .venv __pycache__ */__pycache__
21 changes: 21 additions & 0 deletions changelog.d/20260513_000001_bradsacks99.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Added
-----

- Added uv-native lockfile support via uv.lock.
- Added integration test coverage with a new clamd-backed pytest module.
- Added a GitHub Actions integration workflow that provisions clamd and runs integration tests.
- Added Makefile targets for integration testing and consolidated quality checks.

Changed
-------

- Migrated project packaging metadata from Poetry configuration to PEP 621 project metadata in pyproject.toml.
- Updated local tooling to run through uv (Makefile and pre-commit hooks).
- Updated mypy configuration to target Python 3.12.
- Improved unit test coverage for connection success paths, stream size-limit handling, and async loop setup.

Removed
-------

- Removed legacy setup.py in favor of pyproject.toml build metadata.
- Removed the manual behavior test script and replaced it with automated pytest integration tests.
6 changes: 6 additions & 0 deletions changelog.d/20260514_000001_bradsacks99.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Fixed
-----

- Updated the 0.1.5 package contents to resolve downstream compatibility issues.
- Confirmed runtime code no longer relies on aiopathlib.AsyncPath and ships without aiopathlib dependency.
- Synchronized package metadata and runtime version constant to 0.1.5.
2 changes: 1 addition & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Global options:

[mypy]
python_version = 3.8
python_version = 3.12
ignore_missing_imports = True
show_error_codes = True
64 changes: 35 additions & 29 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
[tool.poetry]

[project]
name = "Pyvalve"
version = "0.1.4"
version = "0.1.5"
description = "Asyncio python clamav client"
authors = ["Bradley Sacks <bradsacks99@gmail.com>"]
license = {file = "LICENSE"}
authors = [{name = "Bradley Sacks", email = "bradsacks99@gmail.com"}]
license = "Apache-2.0"
readme = "README.md"
homepage = "https://github.com/bradsacks99/pyvalve"
documentation = "https://github.com/bradsacks99/pyvalve/blob/master/README.md"

[tool.poetry.dependencies]
python = ">=3.12,<4.0"
asyncinit = "^0.2.4"
aiofile = "^3.8.1"
aiopathlib = "^0.5.0"
aiopath = "^0.7.7"

[tool.poetry.dev-dependencies]
pytest = "^8.3.4"
pytest-pylint = "^0.21.0"
pytest-mypy = "^0.10.3"
pytest-asyncio = "^0.25.0"
pre-commit = "^4.0.1"
pytest-cov = "^6.0.0"
scriv = {extras = ["toml"], version = "^0.16.0"}
sphinx-autodoc-typehints = "^1.19.2"
sphinx-markdown-builder = "^0.5.5"
Sphinx = "^5.1.1"
requires-python = ">=3.12,<4.0"
dependencies = [
"asyncinit>=0.2.4",
"aiofile>=3.8.1",
"pre-commit>=4.1.0"
]

[project.optional-dependencies]
dev = [
Comment on lines +12 to +17
"pytest>=8.3.4",
"pytest-pylint>=0.21.0",
"pytest-mypy>=0.10.3",
"pytest-asyncio>=0.25.0",
"pytest-cov>=6.0.0",
"scriv[toml]>=0.16.0",
"sphinx-autodoc-typehints>=1.19.2",
"sphinx-markdown-builder>=0.5.5",
"Sphinx>=5.1.1"
]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"


[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
markers = [
"integration: tests that require a running clamd service"
]

[package]
include = ["src/*.py"]

[scriv]
version = "literal: src/pyvalve/__init__.py: __version__"
Expand Down
27 changes: 0 additions & 27 deletions setup.py

This file was deleted.

30 changes: 2 additions & 28 deletions src/pyvalve/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@
""" Pyvalve clamd client library """
import asyncio
import struct
import codecs
from io import BytesIO, BufferedReader
from typing import List, BinaryIO
from typing import BinaryIO
from asyncinit import asyncinit
from aiopath import AsyncPath

__version__ = "0.1.3"
__version__ = "0.1.5"
DEBUG = 0

def print_(msg: str) -> None:
Expand Down Expand Up @@ -132,10 +129,7 @@ async def scan(self, path: str) -> str:
:param path str: Path to file/directory to be scanned
:return: Response from clamav
:rtype: str
:raises PyvalveScanningError: If path is not found
"""
if not await self.check_path(path):
raise PyvalveScanningError(f'Path not found: {path}')
return await self.send_command('SCAN', path)

async def contscan(self, path: str) -> str:
Expand All @@ -145,10 +139,7 @@ async def contscan(self, path: str) -> str:
:param path str: Path to file/directory to be scanned
:return: Response from clamav
:rtype: str
:raises PyvalveScanningError: If path is not found
"""
if not await self.check_path(path):
raise PyvalveScanningError(f'Path not found: {path}')
return await self.send_command('CONTSCAN', path)

async def multiscan(self, path: str) -> str:
Expand All @@ -158,10 +149,7 @@ async def multiscan(self, path: str) -> str:
:param path str: Path to file/directory to be scanned
:return: Response from clamav
:rtype: str
:raises PyvalveScanningError: If path is not found
"""
if not await self.check_path(path):
raise PyvalveScanningError(f'Path not found: {path}')
return await self.send_command('MULTISCAN', path)

async def allmatchscan(self, path: str) -> str:
Expand All @@ -171,10 +159,7 @@ async def allmatchscan(self, path: str) -> str:
:param path str: Path to file/directory to be scanned
:return: Response from clamav
:rtype: str
:raises PyvalveScanningError: If path is not found
"""
if not await self.check_path(path):
raise PyvalveScanningError(f'Path not found: {path}')
return await self.send_command('ALLMATCHSCAN', path)

async def send_command(self,
Expand Down Expand Up @@ -271,17 +256,6 @@ async def close(self) -> None:
self.conn.writer.close()
await self.conn.writer.wait_closed()

async def check_path(self, path: str) -> bool:
"""
Check scanning path

:param path str: Path to file/directory to be scanned
"""
print_(f'Checking path {path}')
chk_path = AsyncPath(path)

return await chk_path.exists()

async def get_connection(self):
""" Place holder get_connection method """
raise NotImplementedError("Must override get_connection")
Expand Down
Loading
Loading