Skip to content

Commit b8fd192

Browse files
Merge pull request #40 from UIUCLibrary/json-decode-error
feat: --verbose option added to tripwire metadata validate
2 parents bfd62e1 + e38e15b commit b8fd192

15 files changed

Lines changed: 542 additions & 259 deletions

Jenkinsfile

Lines changed: 58 additions & 75 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,27 @@ documentation = "https://uiuclibrary.github.io/tripwire/"
2626
tripwire = "uiucprescon.tripwire.main:main"
2727

2828
[dependency-groups]
29-
test = ["pytest>7", "coverage"]
29+
test = ["pytest>7"]
3030
docs = ["sphinx"]
3131
audit-dependencies = ['uv-secure']
32+
type-checking = ["mypy", "types-tqdm"]
33+
lint = ["ruff"]
34+
tox = ["tox"]
35+
tox-uv = [
36+
{include-group = "tox"},
37+
"tox-uv-bare"
38+
]
3239
dev = [
3340
"lxml",
34-
"mypy",
3541
"pre-commit",
3642
"pytest",
37-
"ruff",
38-
"tox",
39-
"types-tqdm",
4043
{include-group = "audit-dependencies"},
4144
{include-group = "docs"},
45+
{include-group = "lint"},
4246
{include-group = "test"},
47+
{include-group = "tox"},
48+
{include-group = "type-checking"},
49+
"coverage"
4350
]
4451
deploy = ["twine"]
4552
ci = ["pysonar", {include-group = "dev"}]
@@ -68,6 +75,7 @@ pre_bump_hooks = [
6875
[tool.ruff]
6976
line-length = 79
7077
extend-exclude = ["tests"]
78+
include = ["src/**/*.py"]
7179

7280
[tool.ruff.lint]
7381
extend-select = [
@@ -90,6 +98,44 @@ description = "Run test under {base_python}"
9098
commands = [["{env_bin_dir}{/}pytest"]]
9199
dependency_groups = ['test']
92100

101+
[tool.tox.env.type-checking]
102+
description = "Run mypy type checking"
103+
dependency_groups = ['type-checking']
104+
commands = [["{env_bin_dir}{/}mypy", "-p", "uiucprescon.tripwire"]]
105+
106+
[tool.tox.env.lint]
107+
description = "Run ruff linting"
108+
dependency_groups = ['lint']
109+
skip_install = true
110+
commands = [["{env_bin_dir}{/}ruff", "check"]]
111+
112+
[tool.tox.env.docs]
113+
description = "Build documentation"
114+
dependency_groups = ['docs']
115+
commands = [
116+
[
117+
"{env_bin_dir}{/}sphinx-build", "docs", "{temp_dir}{/}build{/}docs",
118+
"--fail-on-warning",
119+
"--keep-going",
120+
"--write-all",
121+
"--fresh-env"
122+
]
123+
]
124+
125+
[tool.tox.env.docs-linkcheck]
126+
description = "Build documentation"
127+
dependency_groups = ['docs']
128+
commands = [
129+
[
130+
"{env_bin_dir}{/}sphinx-build", "docs", "{temp_dir}{/}build{/}docs-link-check",
131+
"--fail-on-warning",
132+
"--keep-going",
133+
"--write-all",
134+
"--fresh-env",
135+
"--builder=linkcheck"
136+
]
137+
]
138+
93139
[tool.mypy]
94140
mypy_path = "src"
95141

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Exceptions used in the tripwire package.
2+
3+
.. versionadded:: 0.3.7
4+
Added Exceptions module and relocated InvalidFileFormat to here
5+
"""
6+
7+
8+
class TripwireException(Exception):
9+
"""Base class for all exceptions raised by this module."""
10+
11+
12+
class InvalidFileFormat(TripwireException):
13+
"""Invalid file format exception.
14+
15+
.. versionchanged:: 0.3.7
16+
relocate from uiucprescon.tripwire.files to here
17+
"""
18+
19+
def __init__(self, file: str = "", details: str = "") -> None:
20+
"""Initialize exception.
21+
22+
Args:
23+
file: path of the file that caused the exception. Optional.
24+
details: details of the exception. Optional.
25+
"""
26+
message = (
27+
f"Invalid file format. File: {file}"
28+
if file
29+
else "Invalid file format"
30+
)
31+
if details:
32+
message = f"{message}. Details: {details}"
33+
super().__init__(message)
34+
self.file_name = file
35+
self.details = details

src/uiucprescon/tripwire/files.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
"""File handling for files used by tripwire."""
1+
"""File handling for files used by tripwire.
2+
3+
.. versionchanged:: 0.3.7
4+
InvalidFileFormat now located in uiucprescon.tripwire.exceptions
5+
6+
"""
27

38
import abc
49
import collections
@@ -35,22 +40,6 @@
3540
logger.setLevel(logging.INFO)
3641

3742

38-
class InvalidFileFormat(Exception):
39-
"""Invalid file format exception."""
40-
41-
def __init__(self, file: str = "", details: str = "") -> None:
42-
message = (
43-
f"Invalid file format. File: {file}"
44-
if file
45-
else "Invalid file format"
46-
)
47-
if details:
48-
message = f"{message}. Details: {details}"
49-
super().__init__(message)
50-
self.file_name = file
51-
self.details = details
52-
53-
5443
@dataclasses.dataclass(frozen=True)
5544
class TableRow:
5645
"""Table row."""
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""Introspection utilities."""
2+
3+
from importlib import metadata
4+
5+
__all__ = ["get_application_info"]
6+
7+
8+
def get_application_info() -> str:
9+
"""Get application info."""
10+
description = get_application_description()
11+
packages_report = (
12+
f"\nInstalled Python packages:\n\n{get_install_packages_info()}"
13+
)
14+
return "\n".join(list(filter(None, [description, packages_report])))
15+
16+
17+
def get_application_description() -> str:
18+
return (
19+
"Tripwire is a tool for validating and processing media manifests. "
20+
"It is developed by the University of Illinois Urbana-Champaign "
21+
"Library's Preservation Services Department."
22+
)
23+
24+
25+
def get_install_packages_info() -> str:
26+
return "\n".join(
27+
[
28+
f" {x.metadata['Name']}, version: {x.metadata['Version']}"
29+
for x in sorted(
30+
metadata.distributions(),
31+
key=lambda x: x.metadata["Name"].upper(),
32+
)
33+
]
34+
)

src/uiucprescon/tripwire/main.py

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@
22
# PYTHON_ARGCOMPLETE_OK
33

44
import argparse
5+
import contextlib
56
import functools
67
import logging
78
import pathlib
89
import sys
910
from typing import Callable, Any, Dict, Tuple, Optional
1011

11-
from uiucprescon.tripwire import validation, utils, manifest_check, metadata
12-
from uiucprescon.tripwire.files import InvalidFileFormat
12+
from uiucprescon.tripwire import (
13+
validation,
14+
utils,
15+
manifest_check,
16+
metadata,
17+
introspection,
18+
)
19+
from uiucprescon.tripwire.exceptions import InvalidFileFormat
1320
import argcomplete
1421

1522
logger = logging.getLogger(__name__)
@@ -121,10 +128,22 @@ def get_arg_parser() -> Tuple[
121128

122129
metadata_show_show.add_argument("glob", type=str)
123130

124-
metadata_validate = metadata_parser.add_parser("validate")
131+
metadata_validate = metadata_parser.add_parser(
132+
"validate", help="validate files from mediaconch policy"
133+
)
125134
metadata_validate.add_argument("policy_file", type=pathlib.Path)
126135
metadata_validate.add_argument("glob", type=str)
127-
136+
metadata_validate.add_argument(
137+
"-v",
138+
"--verbose",
139+
action="count",
140+
default=1,
141+
help="increase output verbosity",
142+
dest="verbosity",
143+
)
144+
sub_commands.add_parser(
145+
"info", help="get information about current version of tripwire"
146+
)
128147
return (
129148
parser,
130149
{
@@ -142,14 +161,56 @@ def metadata_show_command(args: argparse.Namespace) -> None:
142161
metadata.show_metadata(args.glob, search_path=pathlib.Path("."))
143162

144163

164+
@contextlib.contextmanager
165+
def module_logging_verbosity(logger, verbosity=logging.INFO):
166+
"""Set logging level to verbosity for a module.
167+
168+
This is useful for setting the logging level for a module to a specific
169+
level for the duration of a block of code, and then resetting it back after
170+
leaving the decorated function's scope.
171+
"""
172+
starting_verbosity = logger.getEffectiveLevel()
173+
handler_levels = {}
174+
try:
175+
logger.setLevel(verbosity)
176+
for handler in logger.handlers:
177+
handler_levels[handler.name] = handler.level
178+
handler.setLevel(verbosity)
179+
yield
180+
finally:
181+
logger.setLevel(starting_verbosity)
182+
for handler in logger.handlers:
183+
if handler.name in handler_levels:
184+
handler.setLevel(handler_levels[handler.name])
185+
186+
145187
@capture_log(logger=metadata.logger)
146-
def metadata_validate_command(args: argparse.Namespace) -> None:
188+
def metadata_validate_command(
189+
args: argparse.Namespace,
190+
validate_metadata_strategy=metadata.validate_metadata,
191+
) -> None:
147192
"""Run metadata validate command."""
148-
if not metadata.validate_metadata(
149-
args.glob, policy_xml_file=args.policy_file
193+
194+
def get_log_level(verbosity: int) -> int:
195+
if verbosity > 2:
196+
print("verbosity level to max, defaulting to DEBUG")
197+
return logging.DEBUG
198+
else:
199+
match args.verbosity:
200+
case 1:
201+
return logging.INFO
202+
case 2:
203+
return logging.DEBUG
204+
return logging.INFO
205+
206+
with module_logging_verbosity(
207+
metadata.logger, verbosity=get_log_level(args.verbosity)
150208
):
151-
print("failed metadata validation")
152-
sys.exit(1)
209+
if not validate_metadata_strategy(
210+
args.glob, policy_xml_file=args.policy_file
211+
):
212+
print("failed metadata validation")
213+
sys.exit(1)
153214

154215
print("passed metadata validation")
155216

@@ -165,6 +226,11 @@ def metadata_command(args: argparse.Namespace, subcommand: str) -> None:
165226
raise ValueError(f"Unknown metadata subcommand: {subcommand}")
166227

167228

229+
def show_info_command() -> None:
230+
"""Show info about application."""
231+
print(introspection.get_application_info())
232+
233+
168234
def main() -> None:
169235
"""Main entry point for the Tripwire command line interface."""
170236
parser, print_help_commands = get_arg_parser()
@@ -182,6 +248,8 @@ def main() -> None:
182248
)
183249
case "metadata":
184250
metadata_command(args, args.metadata_command)
251+
case "info":
252+
show_info_command()
185253

186254

187255
if __name__ == "__main__":

src/uiucprescon/tripwire/manifest_check.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
)
1717
import logging
1818
from uiucprescon.tripwire import files as tripwire_files
19+
from uiucprescon.tripwire.exceptions import InvalidFileFormat
20+
1921
from tqdm import tqdm
2022

2123
__all__ = ["locate_manifest_files"]
@@ -349,9 +351,7 @@ def locate_manifest_files_fp(
349351
)
350352
manifest = tripwire_files.TSVManifest(manifest_tsv_fp)
351353
if not manifest.is_valid_file():
352-
raise tripwire_files.InvalidFileFormat(
353-
details="Not a valid TSV manifest file."
354-
)
354+
raise InvalidFileFormat(details="Not a valid TSV manifest file.")
355355

356356
scanner = PackageScanner(search_path)
357357
for row in (
@@ -389,8 +389,8 @@ def locate_manifest_files(
389389
unexpected_files = locate_manifest_files_fp(
390390
fp, search_path, manifest_type=manifest_type
391391
)
392-
except tripwire_files.InvalidFileFormat as e:
393-
raise tripwire_files.InvalidFileFormat(
392+
except InvalidFileFormat as e:
393+
raise InvalidFileFormat(
394394
file=manifest_tsv.name, details=e.details
395395
) from e
396396
if unexpected_files:

0 commit comments

Comments
 (0)