• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IIIF / presentation-validator / 23324152613

20 Mar 2026 12:49AM UTC coverage: 77.088% (+2.2%) from 74.85%
23324152613

Pull #199

github

glenrobson
Future proofing for v4
Pull Request #199: Refactoring and using uv

451 of 548 new or added lines in 10 files covered. (82.3%)

4 existing lines in 1 file now uncovered.

720 of 934 relevant lines covered (77.09%)

3.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.39
/presentation_validator/cli.py
1
import argparse
4✔
2
import sys
4✔
3
from urllib.parse import urlparse
4✔
4
from presentation_validator.web import create_app
4✔
5
from presentation_validator.validator import check_manifest, fetch_manifest
4✔
6
from presentation_validator.enum import IIIFVersion
4✔
7
from bottle import run
4✔
8
from pathlib import Path
4✔
9

10
import json
4✔
11
import requests
4✔
12

13
def is_url(value: str) -> bool:
4✔
NEW
14
    parsed = urlparse(value)
×
NEW
15
    return parsed.scheme in ("http", "https")
×
16

17
def load_input(source: str):
4✔
NEW
18
    warnings = []
×
NEW
19
    """Load JSON from a file path or URL."""
×
NEW
20
    if is_url(source):
×
NEW
21
        manifest, warnings = fetch_manifest(source, True, None)
×
NEW
22
        return manifest, warnings
×
23
    else:
NEW
24
        with open(source, "r", encoding="utf-8") as f:
×
NEW
25
            return json.load(f), warnings
×
26

27

28
def run_validate(args):
4✔
NEW
29
    try:
×
NEW
30
        data, warnings = load_input(args.source)
×
NEW
31
    except Exception as e:
×
NEW
32
        print(f"❌ Failed to load input: {e}", file=sys.stderr)
×
NEW
33
        sys.exit(1)
×
34

NEW
35
    version = IIIFVersion.from_string(args.version)
×
36

NEW
37
    try:
×
NEW
38
        result = check_manifest(data, version, args.source, warnings)
×
NEW
39
    except Exception as e:
×
NEW
40
        print(f"❌ Validation error: {e}", file=sys.stderr)
×
NEW
41
        sys.exit(1)
×
42

43
    # Pretty print JSON result
NEW
44
    print(json.dumps(result.json(), indent=2))
×
45

46
    # Optional: exit non-zero if invalid
NEW
47
    if isinstance(result, dict) and result.get("okay") is False:
×
NEW
48
        sys.exit(2)
×
49

50

51
def run_serve(args):
4✔
NEW
52
    app = create_app()
×
53

NEW
54
    run(
×
55
        app,
56
        host=args.host,
57
        port=args.port,
58
        debug=args.debug,
59
        reloader=args.reload,
60
    )
61

62
def run_validate_dir(args):
4✔
63
    # open up the directory
64
    base = Path(args.directory)
4✔
65

66
    if not base.exists() or not base.is_dir():
4✔
NEW
67
        print(f"Error: '{base}' is not a valid directory")
×
NEW
68
        return 1
×
69

70
    # find all files with the specified extension
71
    json_files = list(base.rglob(f"*{args.extension.replace('*','')}"))
4✔
72

73
    print(f"Found {len(json_files)} files with extension '{args.extension}'\n")
4✔
74

75
    # validate each file
76
    total = 0
4✔
77
    passed = 0
4✔
78
    failed = 0
4✔
79

80
    for path in json_files:
4✔
81
        total += 1
4✔
82
        print(f"Validating: {path}")
4✔
83

84
        try:
4✔
85
            with path.open("r", encoding="utf-8") as f:
4✔
86
                data = json.load(f)
4✔
87

88
            result = check_manifest(data, IIIFVersion.from_string(args.version))
4✔
89

90
            # print results
91
            if result.passed:
4✔
92
                passed += 1
4✔
93
                print("  ✓ OK\n")
4✔
94
            else:
95
                failed += 1
4✔
96
                if result.error:
4✔
NEW
97
                    print(f"  ✗ FAIL: {result.error}\n")
×
98
                else:
99
                    print("  ✗ FAIL\n")
4✔
100
                    for error in result.errorList:
4✔
101
                        print(str(error))
4✔
102

103
                for w in result.warnings:
4✔
NEW
104
                    print(f"    ⚠ {w}")
×
105

NEW
106
        except Exception as e:
×
NEW
107
            failed += 1
×
NEW
108
            print(f"  ✗ ERROR: {e}")
×
109

110
    # summary
111
    print("\n--- Summary ---")
4✔
112
    print(f"Total:  {total}")
4✔
113
    print(f"Passed: {passed}")
4✔
114
    print(f"Failed: {failed}")
4✔
115

116
    return 0 if failed == 0 else 1
4✔
117

118
def main():
4✔
NEW
119
    parser = argparse.ArgumentParser(
×
120
        prog="iiif-validator",
121
        description="IIIF Presentation Validator",
122
    )
123

NEW
124
    subparsers = parser.add_subparsers(dest="command", required=True)
×
125

126
    # ---- validate ----
NEW
127
    validate_parser = subparsers.add_parser(
×
128
        "validate", help="Validate a IIIF manifest from file or URL"
129
    )
NEW
130
    validate_parser.add_argument(
×
131
        "source",
132
        help="Path or URL to IIIF manifest JSON",
133
    )
NEW
134
    validate_parser.add_argument(
×
135
        "--version",
136
        type=IIIFVersion.from_string,
137
        choices=[v.value for v in IIIFVersion],
138
        help=f"IIIF Presentation version ({IIIFVersion.values_str()})",
139
        default=None,
140
    )
NEW
141
    validate_parser.set_defaults(func=run_validate)
×
142

143
    # ---- serve ----
NEW
144
    serve_parser = subparsers.add_parser(
×
145
        "serve", help="Run the validator web server"
146
    )
NEW
147
    serve_parser.add_argument("--host", default="127.0.0.1")
×
NEW
148
    serve_parser.add_argument("--port", type=int, default=8080)
×
NEW
149
    serve_parser.add_argument("--debug", action="store_true")
×
NEW
150
    serve_parser.add_argument("--reload", action="store_true")
×
NEW
151
    serve_parser.set_defaults(func=run_serve)
×
152

153
    # --- validate dir --- 
NEW
154
    dir_parser = subparsers.add_parser(
×
155
        "validate-dir",
156
        help="Validate all JSON manifests in a directory (recursively)",
157
    )
158

NEW
159
    dir_parser.add_argument(
×
160
        "directory",
161
        help="Directory to search for JSON files",
162
    )
163

NEW
164
    dir_parser.add_argument(
×
165
        "--version",
166
        type=IIIFVersion.from_string,
167
        choices=[v.value for v in IIIFVersion],
168
        help=f"IIIF Presentation version ({IIIFVersion.values_str()})",
169
        default=None,
170
    )
171

NEW
172
    dir_parser.add_argument(
×
173
        "--extension",
174
        default=".json",
175
        help="File extension to look for (default: .json)",
176
    )
177

NEW
178
    dir_parser.set_defaults(func=run_validate_dir)
×
179

NEW
180
    args = parser.parse_args()
×
NEW
181
    return args.func(args) or 0
×
182

183

184

185
if __name__ == "__main__":
4✔
NEW
186
    sys.exit(main())
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc