Skip to content

Commit 447faf4

Browse files
Merge pull request #198 from openstates/people-lint-ignore-warnings-option
People lint: add ignore role warnings option
2 parents 1d4d594 + 5952790 commit 447faf4

5 files changed

Lines changed: 85 additions & 72 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# Changelog
2+
3+
## 6.25.2 - Jun 3, 2026
4+
* Adds an --ignore-role-warnings flag to people lint CLI to reduce verbosity
5+
6+
## 6.25.1 - May 5, 2026
27
* Fixes a syntax error that caused a bug when POSTing data in http-resilience mode, if
38
supplying headers or other kwargs.
49

openstates/cli/people.py

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def print_summary(self) -> None: # pragma: no cover
127127
click.secho(name, bold=True)
128128
for type, count in collection.items():
129129
click.secho(
130-
f" {type:<25} {count:4d} {count/self.person_count*100:.0f}% "
130+
f" {type:<25} {count:4d} {count / self.person_count * 100:.0f}% "
131131
)
132132
else:
133133
click.secho(name + " - none", bold=True)
@@ -251,7 +251,7 @@ def write_csv(files: list[Path], jurisdiction_id: str, output_filename: str) ->
251251

252252

253253
def lint_dir(
254-
abbr: str, verbose: bool, municipal: bool, date: str, fix: bool, save_all: bool
254+
abbr: str, verbose: bool, municipal: bool, date: str, fix: bool, save_all: bool, ignore_role_warnings: bool
255255
) -> int: # pragma: no cover
256256
state_dir = get_data_path(abbr)
257257
legislative_filenames = (state_dir / "legislature").glob("*.yml")
@@ -265,7 +265,7 @@ def lint_dir(
265265
settings = yaml.safe_load(f)
266266

267267
try:
268-
validator = Validator(abbr, settings, fix, save_all)
268+
validator = Validator(abbr, settings, fix, save_all, ignore_role_warnings)
269269
except BadVacancy:
270270
sys.exit(-1)
271271

@@ -398,17 +398,17 @@ def load_directory_to_database(files: list[Path], purge: bool) -> None:
398398

399399

400400
def create_person(
401-
fname: str,
402-
lname: str,
403-
name: str,
404-
state: str,
405-
district: str,
406-
party: str,
407-
rtype: str,
408-
url: str,
409-
image: str,
410-
email: str,
411-
start_date: str,
401+
fname: str,
402+
lname: str,
403+
name: str,
404+
state: str,
405+
district: str,
406+
party: str,
407+
rtype: str,
408+
url: str,
409+
image: str,
410+
email: str,
411+
start_date: str,
412412
) -> None:
413413
role = Role(
414414
type=rtype,
@@ -497,17 +497,17 @@ def to_csv(abbreviations: list[str], upload: bool) -> None:
497497
@click.option("--email", prompt="Email", help="Email")
498498
@click.option("--start-date", prompt="Start Date", help="Start Date YYYY-MM-DD")
499499
def new(
500-
fname: str,
501-
lname: str,
502-
name: str,
503-
state: str,
504-
district: str,
505-
party: str,
506-
rtype: str,
507-
url: str,
508-
image: str,
509-
email: str,
510-
start_date: str,
500+
fname: str,
501+
lname: str,
502+
name: str,
503+
state: str,
504+
district: str,
505+
party: str,
506+
rtype: str,
507+
url: str,
508+
image: str,
509+
email: str,
510+
start_date: str,
511511
) -> None:
512512
"""
513513
Create a new person record.
@@ -558,11 +558,11 @@ def summarize(abbreviations: list[str], roster: bool) -> None:
558558
@click.option("--death", is_flag=True)
559559
@click.option("--vacant", is_flag=True)
560560
def retire(
561-
date: str,
562-
filenames: list[str],
563-
reason: typing.Optional[str],
564-
death: bool,
565-
vacant: bool,
561+
date: str,
562+
filenames: list[str],
563+
reason: typing.Optional[str],
564+
death: bool,
565+
vacant: bool,
566566
) -> None:
567567
"""
568568
Retire a legislator, given END_DATE and FILENAME.
@@ -623,7 +623,7 @@ def sync_images(abbreviations: list[str], skip_existing: bool) -> None:
623623
@click.option(
624624
"--save-all/--no-save-all",
625625
default=False,
626-
help="Enable/disable automatic reforamting of YAML.",
626+
help="Enable/disable automatic reformatting of YAML.",
627627
)
628628
@click.option(
629629
"--municipal/--no-municipal",
@@ -636,13 +636,19 @@ def sync_images(abbreviations: list[str], skip_existing: bool) -> None:
636636
default=None,
637637
help="Lint roles using a certain date instead of today.",
638638
)
639+
@click.option(
640+
"--ignore-role-warnings/--do-not-ignore-role-warnings",
641+
default=False,
642+
help="Do not emit warnings for people with no active roles.",
643+
)
639644
def lint(
640-
abbreviations: list[str],
641-
verbose: bool,
642-
municipal: bool,
643-
date: str,
644-
fix: bool,
645-
save_all: bool,
645+
abbreviations: list[str],
646+
verbose: bool,
647+
municipal: bool,
648+
date: str,
649+
fix: bool,
650+
save_all: bool,
651+
ignore_role_warnings: bool,
646652
) -> None:
647653
"""
648654
Lint YAML files.
@@ -656,7 +662,7 @@ def lint(
656662

657663
for abbr in abbreviations:
658664
click.secho("==== {} ====".format(abbr), bold=True)
659-
error_count += lint_dir(abbr, verbose, municipal, date, fix, save_all)
665+
error_count += lint_dir(abbr, verbose, municipal, date, fix, save_all, ignore_role_warnings)
660666

661667
if error_count:
662668
click.secho(f"exiting with {error_count} errors", fg="red")
@@ -749,8 +755,8 @@ def merge(abbr: str, input_dir: str, retirement: str, reset_offices: bool) -> No
749755
existing_people: list[Person] = []
750756
directory = get_data_path(abbr)
751757
for filename in itertools.chain(
752-
directory.glob("legislature/*.yml"),
753-
directory.glob("retired/*.yml"),
758+
directory.glob("legislature/*.yml"),
759+
directory.glob("retired/*.yml"),
754760
):
755761
existing_people.append(Person.load_yaml(filename))
756762

openstates/utils/people/lint_people.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ class Missing:
4848

4949

5050
def validate_roles(
51-
person: Person,
52-
roles_key: str,
53-
retired: bool = False,
51+
person: Person,
52+
roles_key: str,
53+
retired: bool = False,
5454
) -> list[str]:
5555
active = [role for role in getattr(person, roles_key) if role.is_active()]
5656
if len(active) == 0 and not retired:
@@ -63,9 +63,10 @@ def validate_roles(
6363

6464

6565
def validate_roles_key(
66-
person: Person,
67-
person_type: PersonType,
68-
fix: bool,
66+
person: Person,
67+
person_type: PersonType,
68+
fix: bool,
69+
ignore_role_warnings: bool,
6970
) -> CheckResult:
7071
resp = CheckResult([], [], [])
7172
role_issues = validate_roles(
@@ -80,7 +81,7 @@ def validate_roles_key(
8081
# municipals missing roles is a warning to avoid blocking lint
8182
if fix:
8283
resp.fixes = [MOVED_TO_RETIRED]
83-
else:
84+
elif not ignore_role_warnings:
8485
resp.warnings.extend(role_issues)
8586
else:
8687
resp.errors.extend(role_issues)
@@ -111,7 +112,7 @@ def validate_offices(person: Person) -> list[str]:
111112
return errors
112113

113114

114-
def validate_name(person: Person, person_type: PersonType, fix: bool) -> CheckResult:
115+
def validate_name(person: Person, person_type: PersonType, fix: bool, ignore_role_warnings: bool) -> CheckResult:
115116
"""some basic checks on a persons name"""
116117
errors = []
117118
fixes = []
@@ -155,7 +156,7 @@ def validate_jurisdictions(person: Person, municipalities: list[str]) -> list[st
155156

156157

157158
def get_expected_districts(
158-
settings: dict[str, dict], abbr: str
159+
settings: dict[str, dict], abbr: str
159160
) -> _EXPECTED_DISTRICTS_TYPE:
160161
expected = {}
161162

@@ -194,7 +195,7 @@ def get_expected_districts(
194195

195196

196197
def compare_districts(
197-
expected: _EXPECTED_DISTRICTS_TYPE, actual: _ACTUAL_DISTRICTS_TYPE
198+
expected: _EXPECTED_DISTRICTS_TYPE, actual: _ACTUAL_DISTRICTS_TYPE
198199
) -> list[str]:
199200
errors = []
200201

@@ -222,9 +223,10 @@ def compare_districts(
222223

223224

224225
class Validator:
225-
def __init__(self, abbr: str, settings: dict, fix: bool, save_all: bool):
226+
def __init__(self, abbr: str, settings: dict, fix: bool, save_all: bool, ignore_role_warnings: bool):
226227
self.fix = fix
227228
self.save_all = save_all
229+
self.ignore_role_warnings = ignore_role_warnings
228230
self.expected = get_expected_districts(settings, abbr)
229231
self.errors: defaultdict[str, list[str]] = defaultdict(list)
230232
self.warnings: defaultdict[str, list[str]] = defaultdict(list)
@@ -244,24 +246,24 @@ def __init__(self, abbr: str, settings: dict, fix: bool, save_all: bool):
244246
raise ValueError(f"invalid municipality id {m}")
245247

246248
def process_validator_result(
247-
self,
248-
validator_func: typing.Callable[[Person, PersonType, bool], CheckResult],
249-
person: Person,
250-
person_type: PersonType,
251-
original_filename: Path,
249+
self,
250+
validator_func: typing.Callable[[Person, PersonType, bool, bool], CheckResult],
251+
person: Person,
252+
person_type: PersonType,
253+
original_filename: Path,
252254
) -> None:
253-
result = validator_func(person, person_type, self.fix)
255+
result = validator_func(person, person_type, self.fix, self.ignore_role_warnings)
254256
self.errors[original_filename.name].extend(result.errors)
255257
self.warnings[original_filename.name].extend(result.warnings)
256258
if result.fixes:
257259
self.fixes[original_filename.name].extend(result.fixes)
258260
dump_obj(person, filename=original_filename)
259261

260262
def validate_person(
261-
self,
262-
data: dict[str, typing.Any],
263-
filename: Path,
264-
person_type: PersonType,
263+
self,
264+
data: dict[str, typing.Any],
265+
filename: Path,
266+
person_type: PersonType,
265267
) -> None:
266268
print_filename = filename.name
267269
try:
@@ -326,9 +328,9 @@ def validate_old_district_names(self, person: Person) -> list[str]:
326328
errors = []
327329
for role in person.roles:
328330
if (
329-
role.district
330-
and role.district not in self.expected[role.type]
331-
and role.district not in self.legacy_districts[role.type]
331+
role.district
332+
and role.district not in self.expected[role.type]
333+
and role.district not in self.legacy_districts[role.type]
332334
):
333335
errors.append(f"unknown district name: {role.type} {role.district}")
334336
return errors

openstates/utils/tests/test_lint.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,21 @@
5454
],
5555
)
5656
def test_validate_name_errors(person, expected):
57-
assert validate_name(person, PersonType.LEGISLATIVE, fix=False).errors == expected
58-
assert validate_name(person, PersonType.LEGISLATIVE, fix=False).warnings == []
59-
assert validate_name(person, PersonType.LEGISLATIVE, fix=False).fixes == []
57+
assert validate_name(person, PersonType.LEGISLATIVE, fix=False, ignore_role_warnings=False).errors == expected
58+
assert validate_name(person, PersonType.LEGISLATIVE, fix=False, ignore_role_warnings=False).warnings == []
59+
assert validate_name(person, PersonType.LEGISLATIVE, fix=False, ignore_role_warnings=False).fixes == []
6060

6161

6262
def test_validate_name_fixes():
6363
person = Person(id=EXAMPLE_OCD_PERSON_ID, name="Phillip Swoozle", roles=[])
64-
result = validate_name(person, PersonType.LEGISLATIVE, fix=True)
64+
result = validate_name(person, PersonType.LEGISLATIVE, fix=True, ignore_role_warnings=False)
6565
assert result.errors == []
6666
assert len(result.fixes) == 2
6767
assert person.given_name == "Phillip"
6868
assert person.family_name == "Swoozle"
6969

7070
# no fixes on an OK name
71-
result = validate_name(person, PersonType.LEGISLATIVE, fix=True)
71+
result = validate_name(person, PersonType.LEGISLATIVE, fix=True, ignore_role_warnings=False)
7272
assert result.errors == result.fixes == []
7373

7474

@@ -244,7 +244,7 @@ def test_compare_districts_overfill():
244244

245245

246246
def test_person_duplicates():
247-
v = Validator("ak", {}, False, False)
247+
v = Validator("ak", {}, False, False, False)
248248

249249
people = [
250250
# duplicates across people
@@ -296,7 +296,7 @@ def test_filename_id_test():
296296
name="Jane Smith",
297297
roles=[],
298298
)
299-
v = Validator("ak", {}, False, False)
299+
v = Validator("ak", {}, False, False, False)
300300
v.validate_person(person, Path("bad-filename"), PersonType.LEGISLATIVE)
301301
for err in v.errors["bad-filename"]:
302302
if "not in filename" in err:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "openstates"
3-
version = "6.25.1"
3+
version = "6.25.2"
44
description = "core infrastructure for the openstates project"
55
authors = ["James Turk <dev@jamesturk.net>"]
66
license = "MIT"

0 commit comments

Comments
 (0)