Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Changelog

## 6.25.2 - Jun 3, 2026
* Adds an --ignore-role-warnings flag to people lint CLI to reduce verbosity

## 6.25.1 - May 5, 2026
* Fixes a syntax error that caused a bug when POSTing data in http-resilience mode, if
supplying headers or other kwargs.

Expand Down
86 changes: 46 additions & 40 deletions openstates/cli/people.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def print_summary(self) -> None: # pragma: no cover
click.secho(name, bold=True)
for type, count in collection.items():
click.secho(
f" {type:<25} {count:4d} {count/self.person_count*100:.0f}% "
f" {type:<25} {count:4d} {count / self.person_count * 100:.0f}% "
)
else:
click.secho(name + " - none", bold=True)
Expand Down Expand Up @@ -251,7 +251,7 @@ def write_csv(files: list[Path], jurisdiction_id: str, output_filename: str) ->


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

try:
validator = Validator(abbr, settings, fix, save_all)
validator = Validator(abbr, settings, fix, save_all, ignore_role_warnings)
except BadVacancy:
sys.exit(-1)

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


def create_person(
fname: str,
lname: str,
name: str,
state: str,
district: str,
party: str,
rtype: str,
url: str,
image: str,
email: str,
start_date: str,
fname: str,
lname: str,
name: str,
state: str,
district: str,
party: str,
rtype: str,
url: str,
image: str,
email: str,
start_date: str,
) -> None:
role = Role(
type=rtype,
Expand Down Expand Up @@ -497,17 +497,17 @@ def to_csv(abbreviations: list[str], upload: bool) -> None:
@click.option("--email", prompt="Email", help="Email")
@click.option("--start-date", prompt="Start Date", help="Start Date YYYY-MM-DD")
def new(
fname: str,
lname: str,
name: str,
state: str,
district: str,
party: str,
rtype: str,
url: str,
image: str,
email: str,
start_date: str,
fname: str,
lname: str,
name: str,
state: str,
district: str,
party: str,
rtype: str,
url: str,
image: str,
email: str,
start_date: str,
) -> None:
"""
Create a new person record.
Expand Down Expand Up @@ -558,11 +558,11 @@ def summarize(abbreviations: list[str], roster: bool) -> None:
@click.option("--death", is_flag=True)
@click.option("--vacant", is_flag=True)
def retire(
date: str,
filenames: list[str],
reason: typing.Optional[str],
death: bool,
vacant: bool,
date: str,
filenames: list[str],
reason: typing.Optional[str],
death: bool,
vacant: bool,
) -> None:
"""
Retire a legislator, given END_DATE and FILENAME.
Expand Down Expand Up @@ -623,7 +623,7 @@ def sync_images(abbreviations: list[str], skip_existing: bool) -> None:
@click.option(
"--save-all/--no-save-all",
default=False,
help="Enable/disable automatic reforamting of YAML.",
help="Enable/disable automatic reformatting of YAML.",
)
@click.option(
"--municipal/--no-municipal",
Expand All @@ -636,13 +636,19 @@ def sync_images(abbreviations: list[str], skip_existing: bool) -> None:
default=None,
help="Lint roles using a certain date instead of today.",
)
@click.option(
"--ignore-role-warnings/--do-not-ignore-role-warnings",
default=False,
help="Do not emit warnings for people with no active roles.",
)
def lint(
abbreviations: list[str],
verbose: bool,
municipal: bool,
date: str,
fix: bool,
save_all: bool,
abbreviations: list[str],
verbose: bool,
municipal: bool,
date: str,
fix: bool,
save_all: bool,
ignore_role_warnings: bool,
) -> None:
"""
Lint YAML files.
Expand All @@ -656,7 +662,7 @@ def lint(

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

if error_count:
click.secho(f"exiting with {error_count} errors", fg="red")
Expand Down Expand Up @@ -749,8 +755,8 @@ def merge(abbr: str, input_dir: str, retirement: str, reset_offices: bool) -> No
existing_people: list[Person] = []
directory = get_data_path(abbr)
for filename in itertools.chain(
directory.glob("legislature/*.yml"),
directory.glob("retired/*.yml"),
directory.glob("legislature/*.yml"),
directory.glob("retired/*.yml"),
):
existing_people.append(Person.load_yaml(filename))

Expand Down
50 changes: 26 additions & 24 deletions openstates/utils/people/lint_people.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ class Missing:


def validate_roles(
person: Person,
roles_key: str,
retired: bool = False,
person: Person,
roles_key: str,
retired: bool = False,
) -> list[str]:
active = [role for role in getattr(person, roles_key) if role.is_active()]
if len(active) == 0 and not retired:
Expand All @@ -63,9 +63,10 @@ def validate_roles(


def validate_roles_key(
person: Person,
person_type: PersonType,
fix: bool,
person: Person,
person_type: PersonType,
fix: bool,
ignore_role_warnings: bool,
) -> CheckResult:
resp = CheckResult([], [], [])
role_issues = validate_roles(
Expand All @@ -80,7 +81,7 @@ def validate_roles_key(
# municipals missing roles is a warning to avoid blocking lint
if fix:
resp.fixes = [MOVED_TO_RETIRED]
else:
elif not ignore_role_warnings:
resp.warnings.extend(role_issues)
else:
resp.errors.extend(role_issues)
Expand Down Expand Up @@ -111,7 +112,7 @@ def validate_offices(person: Person) -> list[str]:
return errors


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


def get_expected_districts(
settings: dict[str, dict], abbr: str
settings: dict[str, dict], abbr: str
) -> _EXPECTED_DISTRICTS_TYPE:
expected = {}

Expand Down Expand Up @@ -194,7 +195,7 @@ def get_expected_districts(


def compare_districts(
expected: _EXPECTED_DISTRICTS_TYPE, actual: _ACTUAL_DISTRICTS_TYPE
expected: _EXPECTED_DISTRICTS_TYPE, actual: _ACTUAL_DISTRICTS_TYPE
) -> list[str]:
errors = []

Expand Down Expand Up @@ -222,9 +223,10 @@ def compare_districts(


class Validator:
def __init__(self, abbr: str, settings: dict, fix: bool, save_all: bool):
def __init__(self, abbr: str, settings: dict, fix: bool, save_all: bool, ignore_role_warnings: bool):
self.fix = fix
self.save_all = save_all
self.ignore_role_warnings = ignore_role_warnings
self.expected = get_expected_districts(settings, abbr)
self.errors: defaultdict[str, list[str]] = defaultdict(list)
self.warnings: defaultdict[str, list[str]] = defaultdict(list)
Expand All @@ -244,24 +246,24 @@ def __init__(self, abbr: str, settings: dict, fix: bool, save_all: bool):
raise ValueError(f"invalid municipality id {m}")

def process_validator_result(
self,
validator_func: typing.Callable[[Person, PersonType, bool], CheckResult],
person: Person,
person_type: PersonType,
original_filename: Path,
self,
validator_func: typing.Callable[[Person, PersonType, bool, bool], CheckResult],
person: Person,
person_type: PersonType,
original_filename: Path,
) -> None:
result = validator_func(person, person_type, self.fix)
result = validator_func(person, person_type, self.fix, self.ignore_role_warnings)
self.errors[original_filename.name].extend(result.errors)
self.warnings[original_filename.name].extend(result.warnings)
if result.fixes:
self.fixes[original_filename.name].extend(result.fixes)
dump_obj(person, filename=original_filename)

def validate_person(
self,
data: dict[str, typing.Any],
filename: Path,
person_type: PersonType,
self,
data: dict[str, typing.Any],
filename: Path,
person_type: PersonType,
) -> None:
print_filename = filename.name
try:
Expand Down Expand Up @@ -326,9 +328,9 @@ def validate_old_district_names(self, person: Person) -> list[str]:
errors = []
for role in person.roles:
if (
role.district
and role.district not in self.expected[role.type]
and role.district not in self.legacy_districts[role.type]
role.district
and role.district not in self.expected[role.type]
and role.district not in self.legacy_districts[role.type]
):
errors.append(f"unknown district name: {role.type} {role.district}")
return errors
Expand Down
14 changes: 7 additions & 7 deletions openstates/utils/tests/test_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,21 @@
],
)
def test_validate_name_errors(person, expected):
assert validate_name(person, PersonType.LEGISLATIVE, fix=False).errors == expected
assert validate_name(person, PersonType.LEGISLATIVE, fix=False).warnings == []
assert validate_name(person, PersonType.LEGISLATIVE, fix=False).fixes == []
assert validate_name(person, PersonType.LEGISLATIVE, fix=False, ignore_role_warnings=False).errors == expected
assert validate_name(person, PersonType.LEGISLATIVE, fix=False, ignore_role_warnings=False).warnings == []
assert validate_name(person, PersonType.LEGISLATIVE, fix=False, ignore_role_warnings=False).fixes == []


def test_validate_name_fixes():
person = Person(id=EXAMPLE_OCD_PERSON_ID, name="Phillip Swoozle", roles=[])
result = validate_name(person, PersonType.LEGISLATIVE, fix=True)
result = validate_name(person, PersonType.LEGISLATIVE, fix=True, ignore_role_warnings=False)
assert result.errors == []
assert len(result.fixes) == 2
assert person.given_name == "Phillip"
assert person.family_name == "Swoozle"

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


Expand Down Expand Up @@ -244,7 +244,7 @@ def test_compare_districts_overfill():


def test_person_duplicates():
v = Validator("ak", {}, False, False)
v = Validator("ak", {}, False, False, False)

people = [
# duplicates across people
Expand Down Expand Up @@ -296,7 +296,7 @@ def test_filename_id_test():
name="Jane Smith",
roles=[],
)
v = Validator("ak", {}, False, False)
v = Validator("ak", {}, False, False, False)
v.validate_person(person, Path("bad-filename"), PersonType.LEGISLATIVE)
for err in v.errors["bad-filename"]:
if "not in filename" in err:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "openstates"
version = "6.25.1"
version = "6.25.2"
description = "core infrastructure for the openstates project"
authors = ["James Turk <dev@jamesturk.net>"]
license = "MIT"
Expand Down
Loading