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
114 lines (90 loc) · 3.97 KB
/
Copy pathapi.py
File metadata and controls
114 lines (90 loc) · 3.97 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
102
103
104
105
106
107
108
109
110
111
112
113
114
"""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 .schema import SPEC_VERSION
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,
spec_version: str = SPEC_VERSION,
) -> 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. ``spec_version`` selects the TODS spec version to validate
against (schema.SUPPORTED_SPEC_VERSIONS); see docs/spec-versions.md for what
changes between versions. Raises :class:`tods_validate.loader.PackageNotFoundError`
when the package cannot be read at all.
"""
package, findings = run(
path, gtfs, enabled=frozenset(enable), encoding=encoding, spec_version=spec_version
)
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,
spec_version: str = SPEC_VERSION,
) -> 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. Note that fix-value
suggestions (as opposed to the trim/dedupe structural ones) are currently
derived from the v2.1.0 field inventory regardless of ``spec_version``, so
under v1.0.0 they degrade to offering none rather than a wrong one; see
docs/spec-versions.md.
"""
package, findings = run(
path, gtfs, enabled=frozenset(enable), encoding=encoding, spec_version=spec_version
)
return suggest_for_findings(findings, package)
__all__ = ["Suggestion", "ValidationResult", "suggest_fixes", "validate_feed"]