diff --git a/Jenkinsfile b/Jenkinsfile index df622779..bf1a630e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -965,6 +965,21 @@ pipeline { } } } + stage('pyDocStyle'){ + steps{ + catchError(buildResult: 'SUCCESS', message: 'Did not pass all pyDocStyle tests', stageResult: 'UNSTABLE') { + sh( + label: 'Run pydocstyle', + script: 'uv run pydocstyle src/uiucprescon/imagevalidate > reports/pydocstyle-report.txt' + ) + } + } + post { + always{ + recordIssues(tools: [pyDocStyle(pattern: 'reports/pydocstyle-report.txt')]) + } + } + } stage('Run Doctest Tests'){ steps { catchError(buildResult: 'SUCCESS', message: 'Doctest found issues', stageResult: 'UNSTABLE') { @@ -1019,6 +1034,24 @@ pipeline { } } } + stage('Run Pylint Static Analysis') { + steps{ + catchError(buildResult: 'SUCCESS', message: 'Pylint found issues', stageResult: 'UNSTABLE') { + sh( + script: '''mkdir -p logs + mkdir -p reports + uv run pylint src/uiucprescon/imagevalidate -r n --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" > reports/pylint.txt + ''', + label: 'Running pylint' + ) + } + } + post{ + always { + recordIssues(tools: [pyLint(pattern: 'reports/pylint.txt')]) + } + } + } } post{ always{ @@ -1071,12 +1104,12 @@ pipeline { if (env.CHANGE_ID){ sh( label: 'Running Sonar Scanner', - script: "uv run pysonar -t \$token -Dsonar.projectVersion=\$VERSION -Dsonar.buildString=\"${env.BUILD_TAG}\" -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.base=${env.CHANGE_TARGET} -Dsonar.cfamily.cache.enabled=false -Dsonar.cfamily.threads=\$(grep -c ^processor /proc/cpuinfo) -Dsonar.cfamily.build-wrapper-output=build/build_wrapper_output_directory" + script: "uv run pysonar -t \$token -Dsonar.projectVersion=\$VERSION -Dsonar.buildString=\"${env.BUILD_TAG}\" -Dsonar.pullrequest.key=${env.CHANGE_ID} -Dsonar.pullrequest.base=${env.CHANGE_TARGET} -Dsonar.cfamily.cache.enabled=false -Dsonar.cfamily.threads=\$(grep -c ^processor /proc/cpuinfo) -Dsonar.cfamily.build-wrapper-output=build/build_wrapper_output_directory -Dsonar.python.pylint.reportPaths=reports/pylint.txt" ) } else { sh( label: 'Running Sonar Scanner', - script: "uv run pysonar -t \$token -Dsonar.projectVersion=\$VERSION -Dsonar.buildString=\"${env.BUILD_TAG}\" -Dsonar.branch.name=${env.BRANCH_NAME} -Dsonar.cfamily.cache.enabled=false -Dsonar.cfamily.threads=\$(grep -c ^processor /proc/cpuinfo) -Dsonar.cfamily.build-wrapper-output=build/build_wrapper_output_directory" + script: "uv run pysonar -t \$token -Dsonar.projectVersion=\$VERSION -Dsonar.buildString=\"${env.BUILD_TAG}\" -Dsonar.branch.name=${env.BRANCH_NAME} -Dsonar.cfamily.cache.enabled=false -Dsonar.cfamily.threads=\$(grep -c ^processor /proc/cpuinfo) -Dsonar.cfamily.build-wrapper-output=build/build_wrapper_output_directory -Dsonar.python.pylint.reportPaths=reports/pylint.txt" ) } } diff --git a/docs/source/conf.py b/docs/source/conf.py index 0198682e..8a197d7f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -38,7 +38,7 @@ def get_project_metadata(): author = metadata['authors'][0]['name'] # The short X.Y version -version_extractor = re.compile("\d+[.]\d+[.]\d+") +version_extractor = re.compile(r"\d+[.]\d+[.]\d+") version = version_extractor.search(metadata["version"]).group(0) # The full version, including alpha/beta/rc tags. release = metadata["version"] diff --git a/pyproject.toml b/pyproject.toml index 07be6466..ccd93b90 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,11 @@ norecursedirs = "build" markers = "integration" junit_family="xunit2" +[tool.pylint.MAIN] +init-hook = "import sys; sys.path.append('src')" # Replace 'src' with your module directory +extension-pkg-allow-list = ["py3exiv2bind.core", "uiucprescon.imagevalidate.openjp2wrap"] + + [tool.cibuildwheel] test-groups = ["test"] test-command = "pytest {project}/tests" diff --git a/src/uiucprescon/imagevalidate/__init__.py b/src/uiucprescon/imagevalidate/__init__.py index d3fbf471..970a9f21 100644 --- a/src/uiucprescon/imagevalidate/__init__.py +++ b/src/uiucprescon/imagevalidate/__init__.py @@ -2,7 +2,9 @@ from .issues import IssueCategory from .report import Report -from .profile import Profile, available_profiles, get_profile, get_profile_classes +from .profile import ( + Profile, available_profiles, get_profile, get_profile_classes +) from . import profiles __all__ = [ diff --git a/src/uiucprescon/imagevalidate/common.py b/src/uiucprescon/imagevalidate/common.py index df8320b3..4632e2c8 100644 --- a/src/uiucprescon/imagevalidate/common.py +++ b/src/uiucprescon/imagevalidate/common.py @@ -12,6 +12,7 @@ class InvalidStrategy(Exception): class AbsColorSpaceExtractor(metaclass=abc.ABCMeta): + # pylint: disable=too-few-public-methods """Base class for extracting the color space from an image file.""" @abc.abstractmethod @@ -29,6 +30,7 @@ def check(self, image: str) -> str: class ExtractColorSpace: + # pylint: disable=too-few-public-methods """Strategy context for extract color space from a file.""" def __init__(self, strategy: AbsColorSpaceExtractor) -> None: @@ -55,6 +57,7 @@ def check(self, image: str) -> str: class ColorSpaceIccDeviceModelCheck(AbsColorSpaceExtractor): + # pylint: disable=too-few-public-methods """Extract color space by reading the device_model tag in the ICC profile. Useful for identifying sRGB. @@ -74,8 +77,8 @@ def check(self, image: str) -> str: exiv_image = py3exiv2bind.Image(image) try: icc = exiv_image.icc() - except py3exiv2bind.core.NoICCError: - raise InvalidStrategy("Unable to get ICC profile.") + except py3exiv2bind.core.NoICCError as error: + raise InvalidStrategy("Unable to get ICC profile.") from error device_model = icc.get('device_model') if not device_model or \ @@ -85,6 +88,7 @@ def check(self, image: str) -> str: class ColorSpaceIccPrefCcmCheck(AbsColorSpaceExtractor): + # pylint: disable=too-few-public-methods """Extract color space from reading pref_ccm in the ICC profile header.""" def check(self, image: str) -> str: @@ -102,8 +106,9 @@ def check(self, image: str) -> str: try: icc = exiv2_image.icc() except py3exiv2bind.core.NoICCError as error: - raise InvalidStrategy("Unable to get ICC profile." - "Reason: {}".format(error)) + raise InvalidStrategy( + f"Unable to get ICC profile.Reason: {error}" + ) from error pref_ccm = icc.get("pref_ccm") if not pref_ccm or pref_ccm.value.decode("ascii").rstrip(' \0') == '': @@ -112,6 +117,7 @@ def check(self, image: str) -> str: class ColorSpaceOJPCheck(AbsColorSpaceExtractor): + # pylint: disable=too-few-public-methods """Color space extractor using openjpeg library.""" def check(self, image: str) -> str: diff --git a/src/uiucprescon/imagevalidate/issues.py b/src/uiucprescon/imagevalidate/issues.py index 57892a47..d963cf10 100644 --- a/src/uiucprescon/imagevalidate/issues.py +++ b/src/uiucprescon/imagevalidate/issues.py @@ -1,7 +1,11 @@ +"""Module for defining issue categories related to image validation.""" + from enum import Enum class IssueCategory(Enum): + """Enum class defining issue categories.""" + VALID = 0 EMPTY_DATA = 1 MISSING_FIELD = 2 diff --git a/src/uiucprescon/imagevalidate/messages.py b/src/uiucprescon/imagevalidate/messages.py index c439299a..2b710a70 100644 --- a/src/uiucprescon/imagevalidate/messages.py +++ b/src/uiucprescon/imagevalidate/messages.py @@ -6,6 +6,7 @@ class AbsMessage(metaclass=abc.ABCMeta): + # pylint: disable=too-few-public-methods """Base class for messages.""" @abc.abstractmethod @@ -14,6 +15,7 @@ def generate_message(self, field: str, data: report.Result) -> str: class InvalidData(AbsMessage): + # pylint: disable=too-few-public-methods """Invalid data.""" def generate_message(self, field: str, data: report.Result) -> str: @@ -24,6 +26,7 @@ def generate_message(self, field: str, data: report.Result) -> str: class EmptyData(AbsMessage): + # pylint: disable=too-few-public-methods """Empty data.""" def generate_message(self, field: str, data: report.Result) -> str: @@ -32,6 +35,7 @@ def generate_message(self, field: str, data: report.Result) -> str: class MissingField(AbsMessage): + # pylint: disable=too-few-public-methods """Missing fields.""" def generate_message(self, field: str, data: report.Result) -> str: @@ -40,6 +44,7 @@ def generate_message(self, field: str, data: report.Result) -> str: class MessageGenerator: + # pylint: disable=too-few-public-methods """Message Generator.""" def __init__(self, strategy: AbsMessage) -> None: diff --git a/src/uiucprescon/imagevalidate/profile.py b/src/uiucprescon/imagevalidate/profile.py index 79d30168..6f450b61 100644 --- a/src/uiucprescon/imagevalidate/profile.py +++ b/src/uiucprescon/imagevalidate/profile.py @@ -1,15 +1,19 @@ """Profile for validating images.""" +from __future__ import annotations import os import inspect -from typing import Type, Set, Dict -from uiucprescon import imagevalidate +from typing import Type, Set, Dict, TYPE_CHECKING from . import profiles as profile_pkg +if TYPE_CHECKING: + from uiucprescon import imagevalidate + known_profiles: Dict[str, Type[profile_pkg.AbsProfile]] = {} class Profile: + # pylint: disable=too-few-public-methods """Profile loader for validating embedded metadata in image files.""" def __init__(self, validation_profile: profile_pkg.AbsProfile) -> None: @@ -33,6 +37,7 @@ def validate(self, file: str) -> imagevalidate.Report: if not os.path.exists(file): raise FileNotFoundError(f"Unable to locate {file}") return self._profile.validate(file) +# pylint: enable=too-few-public-methods def available_profiles() -> Set[str]: @@ -55,6 +60,7 @@ def get_profile(name: str) -> profile_pkg.AbsProfile: def get_profile_classes(): + """Get all available profiles.""" known_package_profiles: Dict[str, Type[profile_pkg.AbsProfile]] = {} profiles = \ inspect.getmembers( @@ -66,4 +72,5 @@ def get_profile_classes(): known_package_profiles[profile[1].profile_name()] = profile[1] return known_package_profiles + known_profiles = get_profile_classes() diff --git a/src/uiucprescon/imagevalidate/profiles/absProfile.py b/src/uiucprescon/imagevalidate/profiles/absProfile.py index 79d3cc54..bf420c3b 100644 --- a/src/uiucprescon/imagevalidate/profiles/absProfile.py +++ b/src/uiucprescon/imagevalidate/profiles/absProfile.py @@ -1,12 +1,18 @@ """Abstract class for creating a profile.""" +from __future__ import annotations import abc -from typing import Dict, List, Optional, Set +from typing import Dict, List, Optional, Set, TYPE_CHECKING + import py3exiv2bind -from uiucprescon.imagevalidate import Report, IssueCategory, messages +from uiucprescon.imagevalidate import messages +from uiucprescon.imagevalidate.issues import IssueCategory from uiucprescon.imagevalidate.report import Result, ResultCategory +if TYPE_CHECKING: + from uiucprescon.imagevalidate.report import Report + class AbsProfile(metaclass=abc.ABCMeta): """Base class for metadata validation. @@ -14,8 +20,8 @@ class AbsProfile(metaclass=abc.ABCMeta): Implement the validate method when creating new profile """ - expected_metadata_constants: Dict[str, str] = dict() - expected_metadata_any_value: List[str] = list() + expected_metadata_constants: Dict[str, str] = {} + expected_metadata_any_value: List[str] = [] valid_extensions: Set[str] = set() @staticmethod @@ -38,7 +44,7 @@ def validate(self, file: str) -> Report: def _get_metadata_static_values(cls, image: py3exiv2bind.Image) \ -> Dict[str, Result]: - data = dict() + data = {} for key, value in cls.expected_metadata_constants.items(): data[key] = Result( expected=value, @@ -50,7 +56,7 @@ def _get_metadata_static_values(cls, image: py3exiv2bind.Image) \ def _get_metadata_has_values(cls, image: py3exiv2bind.Image) -> \ Dict[str, Result]: - data = dict() + data = {} for key in cls.expected_metadata_any_value: data[key] = Result( expected=ResultCategory.ANY, @@ -75,7 +81,7 @@ def generate_error_msg(category: IssueCategory, field: str, return message_generator.generate_message(field, report_data) - return "Unknown error with {}".format(field) + return f"Unknown error with {field}" @staticmethod def analyze_data_for_issues(result: Result) -> Optional[IssueCategory]: @@ -98,7 +104,7 @@ def get_data_from_image(cls, filename: str) \ -> Dict[str, Result]: """Access data from image.""" image = py3exiv2bind.Image(filename) - data: Dict[str, Result] = dict() + data: Dict[str, Result] = {} data.update(cls._get_metadata_has_values(image)) data.update(cls._get_metadata_static_values(image)) return data diff --git a/src/uiucprescon/imagevalidate/profiles/hathi_common.py b/src/uiucprescon/imagevalidate/profiles/hathi_common.py new file mode 100644 index 00000000..61144867 --- /dev/null +++ b/src/uiucprescon/imagevalidate/profiles/hathi_common.py @@ -0,0 +1,77 @@ +"""Shared values for HathiTrust profiles.""" +from __future__ import annotations + +import collections +import typing +from abc import ABC + +from uiucprescon.imagevalidate import Report +from . import AbsProfile + +if typing.TYPE_CHECKING: + from uiucprescon.imagevalidate import IssueCategory + +__all__ = [ + "SHARED_EXPECTED_METADATA_ANY_VALUE", + "SHARED_EXPECT_RESOLUTION_CONSTANTS" +] + +SHARED_EXPECTED_METADATA_ANY_VALUE = [ + 'Xmp.dc.creator', + + # Address + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr', + + # City + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity', + + # State + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion', + + # Zip code + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode', + + # Country + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry', + + # phone number + 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork', + ] + +SHARED_EXPECT_RESOLUTION_CONSTANTS = { + "Exif.Image.XResolution": "400/1", + "Exif.Image.YResolution": "400/1", +} + + +class AbsValidateHathiTrustProfile(AbsProfile, ABC): + """Profile for validating files for HathiTrust.""" + + def validate(self, file: str) -> Report: + """Validate the image file as a HathiTrust image. + + Args: + file: + File path to an image file + + Returns: + Returns a report of the results. + + """ + report = Report() + report.filename = file + report_data = self.get_data_from_image(file) + report._properties = report_data + + analysis: typing.Dict[IssueCategory, list] = \ + collections.defaultdict(list) + + for key, result in report_data.items(): + issue_category = self.analyze_data_for_issues(result) + if issue_category: + message = self.generate_error_msg(issue_category, key, result) + analysis[issue_category].append(message) + + report._data.update(analysis) + + return report diff --git a/src/uiucprescon/imagevalidate/profiles/hathi_jp2000.py b/src/uiucprescon/imagevalidate/profiles/hathi_jp2000.py index a5d46a66..de5d148b 100644 --- a/src/uiucprescon/imagevalidate/profiles/hathi_jp2000.py +++ b/src/uiucprescon/imagevalidate/profiles/hathi_jp2000.py @@ -1,46 +1,24 @@ """Profile for HathiTrust tiff files.""" -import collections import sys +import typing import py3exiv2bind -import typing -from uiucprescon.imagevalidate import IssueCategory, common -from uiucprescon.imagevalidate import Report +from uiucprescon.imagevalidate import common from uiucprescon.imagevalidate.report import Result from uiucprescon.imagevalidate import openjp2wrap # type: ignore -from . import AbsProfile +from . import hathi_common -class HathiJP2000(AbsProfile): +class HathiJP2000(hathi_common.AbsValidateHathiTrustProfile): """Profile for validating .jp2 files for HathiTrust.""" - expected_metadata_any_value = [ - 'Xmp.dc.creator', - - # Address - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr', - - # City - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity', - - # State - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion', + expected_metadata_any_value =\ + hathi_common.SHARED_EXPECTED_METADATA_ANY_VALUE - # Zip code - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode', + expected_metadata_constants =\ + hathi_common.SHARED_EXPECT_RESOLUTION_CONSTANTS - # Country - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry', - - # phone number - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork', - ] - - expected_metadata_constants = { - "Exif.Image.XResolution": "400/1", - "Exif.Image.YResolution": "400/1", - } valid_extensions = {".jp2"} @staticmethod @@ -48,35 +26,6 @@ def profile_name() -> str: """Get the profile name.""" return "HathiTrust JPEG 2000" - def validate(self, file: str) -> Report: - """Validate the image file as a HathiTrust jpeg2000 image. - - Args: - file: - File path to an image file - - Returns: - Returns a report of the results. - - """ - report = Report() - report.filename = file - report_data = self.get_data_from_image(file) - report._properties = report_data - - analysis: typing.Dict[IssueCategory, list] = \ - collections.defaultdict(list) - - for key, result in report_data.items(): - issue_category = self.analyze_data_for_issues(result) - if issue_category: - message = self.generate_error_msg(issue_category, key, result) - analysis[issue_category].append(message) - - report._data.update(analysis) - - return report - @classmethod def get_data_from_image(cls, filename: str) \ -> typing.Dict[str, Result]: diff --git a/src/uiucprescon/imagevalidate/profiles/hathi_tiff.py b/src/uiucprescon/imagevalidate/profiles/hathi_tiff.py index 39070b6c..f50249e7 100644 --- a/src/uiucprescon/imagevalidate/profiles/hathi_tiff.py +++ b/src/uiucprescon/imagevalidate/profiles/hathi_tiff.py @@ -1,46 +1,29 @@ """Profile for HathiTrust tiff files.""" -import collections +from __future__ import annotations + import sys import typing import py3exiv2bind -from uiucprescon.imagevalidate import IssueCategory -from uiucprescon.imagevalidate import Report, common +from uiucprescon.imagevalidate import common from uiucprescon.imagevalidate.report import Result -from . import AbsProfile +from . import hathi_common -class HathiTiff(AbsProfile): +class HathiTiff(hathi_common.AbsValidateHathiTrustProfile): """Profile for validating Tiff files for HathiTrust.""" - expected_metadata_any_value = [ - 'Xmp.dc.creator', - - # Address - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr', - - # City - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity', - - # State - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion', - - # Zip code - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode', + expected_metadata_any_value =\ + hathi_common.SHARED_EXPECTED_METADATA_ANY_VALUE - # Country - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry', - - # phone number - 'Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork', - ] - - expected_metadata_constants = { - "Exif.Image.XResolution": "400/1", - "Exif.Image.YResolution": "400/1", - 'Exif.Image.BitsPerSample': "8 8 8", - } + expected_metadata_constants = \ + { + **hathi_common.SHARED_EXPECT_RESOLUTION_CONSTANTS, + **{ + 'Exif.Image.BitsPerSample': "8 8 8", + } + } valid_extensions = {".tif"} @staticmethod @@ -48,35 +31,6 @@ def profile_name() -> str: """Get the profile name.""" return "HathiTrust Tiff" - def validate(self, file: str) -> Report: - """Validate the image file as a HathiTrust tiff. - - Args: - file: - File path to an image file - - Returns: - Returns a report of the results. - - """ - report = Report() - report.filename = file - report_data = self.get_data_from_image(file) - report._properties = report_data - - analysis: typing.Dict[IssueCategory, list] = \ - collections.defaultdict(list) - - for key, result in report_data.items(): - issue_category = self.analyze_data_for_issues(result) - if issue_category: - message = self.generate_error_msg(issue_category, key, result) - analysis[issue_category].append(message) - - report._data.update(analysis) - - return report - @classmethod def get_data_from_image(cls, filename: str) -> typing.Dict[str, Result]: """Get data from image.""" diff --git a/src/uiucprescon/imagevalidate/report.py b/src/uiucprescon/imagevalidate/report.py index b791bccd..edf2e23a 100644 --- a/src/uiucprescon/imagevalidate/report.py +++ b/src/uiucprescon/imagevalidate/report.py @@ -1,16 +1,22 @@ """Report generated from validation.""" +from __future__ import annotations -from typing import NamedTuple, Optional, Dict, List, Union +from typing import NamedTuple, Optional, Dict, List, Union, TYPE_CHECKING from enum import Enum -from uiucprescon import imagevalidate +if TYPE_CHECKING: + from uiucprescon import imagevalidate class ResultCategory(Enum): + """Enum class defining result categories.""" + ANY = 0 NONE = 1 class Result(NamedTuple): + """Result class defining result values.""" + expected: Union[str, ResultCategory] actual: Optional[str] @@ -20,10 +26,10 @@ class Report: def __init__(self) -> None: """Access the results.""" - self._properties: Dict[str, Result] = dict() + self._properties: Dict[str, Result] = {} self.filename: Optional[str] = None - self._data: Dict[imagevalidate.IssueCategory, List[str]] = dict() + self._data: Dict[imagevalidate.IssueCategory, List[str]] = {} @property def valid(self) -> bool: @@ -40,7 +46,7 @@ def issues(self, -> List[str]: """Issues or problems discovered.""" if issue_type is not None: - return self._data.get(issue_type, list()) + return self._data.get(issue_type, []) # In issue category is selected, return all return [issue for issues in @@ -53,4 +59,4 @@ def __str__(self) -> str: else: issue_str = "No issues discovered" - return "File: {}\n{}".format(self.filename, issue_str) + return f"File: {self.filename}\n{issue_str}" diff --git a/tox.ini b/tox.ini index 955db9e1..faf9b8e2 100644 --- a/tox.ini +++ b/tox.ini @@ -3,6 +3,8 @@ envlist = py310, py311, py312, py313, py314, py314t min_version = 4.11 [testenv] +labels = + test pass_env = CONAN_USER_HOME INCLUDE @@ -20,28 +22,44 @@ commands= sphinx-build -b doctest -d {envtmpdir}/doctrees docs/source {temp_dir}/doctest {posargs} [testenv:mypy] +labels = + lint + correctness dependency_groups = type_checking setenv = MYPY_CACHE_DIR = {temp_dir}/.mypy_cache commands = mypy {posargs: -p uiucprescon.imagevalidate} [testenv:flake8] description = check the code style +labels = + lint + style dependency_groups = lint skip_install=True commands = flake8 {posargs: src} [testenv:pylint] description = check the code style +labels = + lint + style + correctness dependency_groups = lint skip_install=True commands = pylint {posargs: src} --disable import-error [testenv:pydocstyle] +labels = + lint + style skip_install = true dependency_groups = lint commands = pydocstyle {posargs: {toxinidir}/src} [testenv:bandit] +labels = + lint + security skip_install = true dependency_groups = lint commands = bandit {posargs: --recursive {toxinidir}/src} diff --git a/uv.lock b/uv.lock index c709056b..55dbc72b 100644 --- a/uv.lock +++ b/uv.lock @@ -57,7 +57,7 @@ wheels = [ [[package]] name = "bandit" -version = "1.8.6" +version = "1.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -65,9 +65,9 @@ dependencies = [ { name = "rich" }, { name = "stevedore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/b5/7eb834e213d6f73aace21938e5e90425c92e5f42abafaf8a6d5d21beed51/bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b", size = 4240271, upload-time = "2025-07-06T03:10:50.9Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c3/0cb80dfe0f3076e5da7e4c5ad8e57bac6ac357ff4a6406205501cade4965/bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628", size = 4242677, upload-time = "2026-02-25T06:44:15.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/ca/ba5f909b40ea12ec542d5d7bdd13ee31c4d65f3beed20211ef81c18fa1f3/bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0", size = 133808, upload-time = "2025-07-06T03:10:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z" }, ] [[package]]