forked from ChelseaKR/tods-validate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.py
More file actions
101 lines (77 loc) · 3.34 KB
/
Copy pathapi.py
File metadata and controls
101 lines (77 loc) · 3.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
"""Public, stable Python API.
For callers who want to validate a feed in-process instead of shelling out to
the CLI (for example, a TODS exporter's test suite):
from tods_validate import validate_feed
result = validate_feed("exports/tods", gtfs="exports/gtfs.zip")
if result.error_count:
for finding in result.errors:
print(finding.rule_id, finding.location(), finding.message)
The shapes here (``ValidationResult``, ``Finding``, ``Severity``) follow the
project's semantic-versioning promise: fields are only added within a major
version, never removed or renamed.
"""
from __future__ import annotations
from collections import Counter
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
from .findings import Finding, Severity
from .runner import run
from .suggest import Suggestion, suggest_for_findings
@dataclass(frozen=True)
class ValidationResult:
"""The outcome of validating one feed."""
source: str
findings: list[Finding]
@property
def errors(self) -> list[Finding]:
return [f for f in self.findings if f.severity is Severity.ERROR]
@property
def warnings(self) -> list[Finding]:
return [f for f in self.findings if f.severity is Severity.WARNING]
@property
def infos(self) -> list[Finding]:
return [f for f in self.findings if f.severity is Severity.INFO]
@property
def error_count(self) -> int:
return len(self.errors)
@property
def counts(self) -> Counter[Severity]:
return Counter(f.severity for f in self.findings)
@property
def ok(self) -> bool:
"""True when no errors were found (warnings and info do not count)."""
return self.error_count == 0
def validate_feed(
path: str | Path,
gtfs: str | Path | None = None,
*,
enable: Iterable[str] = (),
encoding: str | None = None,
) -> ValidationResult:
"""Validate the TODS feed at ``path`` and return a :class:`ValidationResult`.
``gtfs`` resolves trip/stop/service/block references; omit it when the GTFS
files sit alongside the TODS files. ``enable`` turns on opt-in rules by ID
or category ("coverage", "advisory", "experimental"). ``encoding`` overrides
UTF-8 decoding. Raises :class:`tods_validate.loader.PackageNotFoundError`
when the package cannot be read at all.
"""
package, findings = run(path, gtfs, enabled=frozenset(enable), encoding=encoding)
return ValidationResult(source=package.source, findings=findings)
def suggest_fixes(
path: str | Path,
gtfs: str | Path | None = None,
*,
enable: Iterable[str] = (),
encoding: str | None = None,
) -> list[Suggestion]:
"""Validate the feed at ``path`` and return concrete fix suggestions for it.
Each :class:`tods_validate.suggest.Suggestion` names a finding the validator
knows how to fix mechanically, classified ``auto`` (safe and meaning-preserving,
the kind ``tods-validate fix`` applies) or ``review`` (derivable but worth a
human's confirmation). Arguments mirror :func:`validate_feed`. Suggestions
never change the feed; applying them is up to the caller.
"""
package, findings = run(path, gtfs, enabled=frozenset(enable), encoding=encoding)
return suggest_for_findings(findings, package)
__all__ = ["Suggestion", "ValidationResult", "suggest_fixes", "validate_feed"]