civics-cdf-validator 1.49.dev9__py3-none-any.whl → 1.49.dev11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- civics_cdf_validator/rules.py +179 -15
- civics_cdf_validator/validator.py +2 -0
- civics_cdf_validator/version.py +1 -1
- {civics_cdf_validator-1.49.dev9.dist-info → civics_cdf_validator-1.49.dev11.dist-info}/METADATA +1 -1
- civics_cdf_validator-1.49.dev11.dist-info/RECORD +15 -0
- {civics_cdf_validator-1.49.dev9.dist-info → civics_cdf_validator-1.49.dev11.dist-info}/WHEEL +1 -1
- civics_cdf_validator-1.49.dev9.dist-info/RECORD +0 -15
- {civics_cdf_validator-1.49.dev9.dist-info → civics_cdf_validator-1.49.dev11.dist-info}/entry_points.txt +0 -0
- {civics_cdf_validator-1.49.dev9.dist-info → civics_cdf_validator-1.49.dev11.dist-info}/licenses/LICENSE-2.0.txt +0 -0
- {civics_cdf_validator-1.49.dev9.dist-info → civics_cdf_validator-1.49.dev11.dist-info}/top_level.txt +0 -0
civics_cdf_validator/rules.py
CHANGED
|
@@ -2898,37 +2898,76 @@ class OfficeSelectionMethodMatch(base.BaseRule):
|
|
|
2898
2898
|
)
|
|
2899
2899
|
|
|
2900
2900
|
|
|
2901
|
+
class OfficeHolderTenureTermDates(base.DateRule):
|
|
2902
|
+
"""OfficeHolderTenure elements should contain valid term dates."""
|
|
2903
|
+
|
|
2904
|
+
def elements(self):
|
|
2905
|
+
return ["OfficeHolderTenure"]
|
|
2906
|
+
|
|
2907
|
+
def check(self, element):
|
|
2908
|
+
start_date = element.find("StartDate")
|
|
2909
|
+
end_date = element.find("EndDate")
|
|
2910
|
+
if element_has_text(start_date) and element_has_text(end_date):
|
|
2911
|
+
start_date_obj = base.PartialDate.init_partial_date(start_date.text)
|
|
2912
|
+
end_date_obj = base.PartialDate.init_partial_date(end_date.text)
|
|
2913
|
+
if end_date_obj < start_date_obj:
|
|
2914
|
+
raise loggers.ElectionError.from_message(
|
|
2915
|
+
"OfficeHolderTenure element has an EndDate that is before the"
|
|
2916
|
+
" StartDate.",
|
|
2917
|
+
[element],
|
|
2918
|
+
)
|
|
2919
|
+
|
|
2920
|
+
|
|
2901
2921
|
class OfficeTermDates(base.DateRule):
|
|
2902
2922
|
"""Office elements should contain valid term dates.
|
|
2903
2923
|
|
|
2904
2924
|
Offices with OfficeHolderPersonIds should have a Term declared. Given
|
|
2905
2925
|
term should have a start date. If term also has an end date then end date
|
|
2906
|
-
should come after start date.
|
|
2926
|
+
should come after start date. Post Office split feed office objects should
|
|
2927
|
+
not have a Term element.
|
|
2907
2928
|
"""
|
|
2908
2929
|
|
|
2930
|
+
def __init__(self, election_tree, schema_tree, **kwargs):
|
|
2931
|
+
self.is_post_office_split_feed = False
|
|
2932
|
+
officeholder_tenure_collection_element = self.get_elements_by_class(
|
|
2933
|
+
election_tree, "OfficeHolderTenureCollection"
|
|
2934
|
+
)
|
|
2935
|
+
if officeholder_tenure_collection_element:
|
|
2936
|
+
self.is_post_office_split_feed = True
|
|
2937
|
+
|
|
2909
2938
|
def elements(self):
|
|
2910
2939
|
return ["Office"]
|
|
2911
2940
|
|
|
2912
2941
|
def check(self, element):
|
|
2913
2942
|
self.reset_instance_vars()
|
|
2914
|
-
|
|
2915
|
-
if element_has_text(off_per_id):
|
|
2943
|
+
if self.is_post_office_split_feed:
|
|
2916
2944
|
term = element.find("Term")
|
|
2917
|
-
if term is None:
|
|
2918
|
-
raise loggers.
|
|
2919
|
-
"
|
|
2945
|
+
if term is not None:
|
|
2946
|
+
raise loggers.ElectionError.from_message(
|
|
2947
|
+
"Office should not contain Term data in post Office split feed.",
|
|
2948
|
+
[element],
|
|
2920
2949
|
)
|
|
2950
|
+
if self.error_log:
|
|
2951
|
+
raise loggers.ElectionError(self.error_log)
|
|
2952
|
+
else:
|
|
2953
|
+
off_per_id = element.find("OfficeHolderPersonIds")
|
|
2954
|
+
if element_has_text(off_per_id):
|
|
2955
|
+
term = element.find("Term")
|
|
2956
|
+
if term is None:
|
|
2957
|
+
raise loggers.ElectionWarning.from_message(
|
|
2958
|
+
"The Office is missing a Term.", [element]
|
|
2959
|
+
)
|
|
2921
2960
|
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2961
|
+
self.gather_dates(term)
|
|
2962
|
+
if self.start_date is None:
|
|
2963
|
+
raise loggers.ElectionWarning.from_message(
|
|
2964
|
+
"The Office is missing a Term > StartDate.", [element]
|
|
2965
|
+
)
|
|
2966
|
+
elif self.end_date is not None:
|
|
2967
|
+
self.check_end_after_start()
|
|
2929
2968
|
|
|
2930
|
-
|
|
2931
|
-
|
|
2969
|
+
if self.error_log:
|
|
2970
|
+
raise loggers.ElectionError(self.error_log)
|
|
2932
2971
|
|
|
2933
2972
|
|
|
2934
2973
|
class RemovePersonAndOfficeHolderId60DaysAfterEndDate(base.TreeRule):
|
|
@@ -4451,6 +4490,118 @@ class UnsupportedOfficeHolderTenureSchema(base.BaseRule):
|
|
|
4451
4490
|
)
|
|
4452
4491
|
|
|
4453
4492
|
|
|
4493
|
+
class ElectoralCommissionCollectionExists(base.BaseRule):
|
|
4494
|
+
"""ElectoralCommissionCollection should exist."""
|
|
4495
|
+
|
|
4496
|
+
def elements(self):
|
|
4497
|
+
return ["ElectionReport"]
|
|
4498
|
+
|
|
4499
|
+
def check(self, element):
|
|
4500
|
+
if element.find("ElectoralCommissionCollection") is None:
|
|
4501
|
+
raise loggers.ElectionError.from_message(
|
|
4502
|
+
"ElectoralCommissionCollection should exist."
|
|
4503
|
+
)
|
|
4504
|
+
|
|
4505
|
+
|
|
4506
|
+
class VoterInformationCollectionExists(base.BaseRule):
|
|
4507
|
+
"""Warn if there is no VoterInformationCollection."""
|
|
4508
|
+
|
|
4509
|
+
def elements(self):
|
|
4510
|
+
return ["ElectionReport"]
|
|
4511
|
+
|
|
4512
|
+
def check(self, element):
|
|
4513
|
+
if element.find("VoterInformationCollection") is None:
|
|
4514
|
+
raise loggers.ElectionWarning.from_message(
|
|
4515
|
+
"VoterInformationCollection should exist."
|
|
4516
|
+
)
|
|
4517
|
+
|
|
4518
|
+
|
|
4519
|
+
class NoExtraElectionElements(base.BaseRule):
|
|
4520
|
+
"""Elections for voter information feeds should not have certain elements.
|
|
4521
|
+
|
|
4522
|
+
BallotStyleCollection, CandidateCollection, ContestCollection, CountStatus
|
|
4523
|
+
should all be excluded.
|
|
4524
|
+
"""
|
|
4525
|
+
|
|
4526
|
+
def elements(self):
|
|
4527
|
+
return ["Election"]
|
|
4528
|
+
|
|
4529
|
+
def check(self, element):
|
|
4530
|
+
if element.find("BallotStyleCollection") is not None:
|
|
4531
|
+
raise loggers.ElectionError.from_message(
|
|
4532
|
+
"BallotStyleCollection should not exist."
|
|
4533
|
+
)
|
|
4534
|
+
|
|
4535
|
+
if element.find("CandidateCollection") is not None:
|
|
4536
|
+
raise loggers.ElectionError.from_message(
|
|
4537
|
+
"CandidateCollection should not exist."
|
|
4538
|
+
)
|
|
4539
|
+
if element.find("ContestCollection") is not None:
|
|
4540
|
+
raise loggers.ElectionError.from_message(
|
|
4541
|
+
"ContestCollection should not exist."
|
|
4542
|
+
)
|
|
4543
|
+
|
|
4544
|
+
if element.find("CountStatus") is not None:
|
|
4545
|
+
raise loggers.ElectionError.from_message("CountStatus should not exist.")
|
|
4546
|
+
|
|
4547
|
+
|
|
4548
|
+
class WarnOnElementsNotRecommendedForElection(base.BaseRule):
|
|
4549
|
+
"""Warn on ContactInformation on an Election for voter information feeds."""
|
|
4550
|
+
|
|
4551
|
+
def elements(self):
|
|
4552
|
+
return ["Election"]
|
|
4553
|
+
|
|
4554
|
+
def check(self, element):
|
|
4555
|
+
if element.find("ContactInformation") is not None:
|
|
4556
|
+
raise loggers.ElectionWarning.from_message(
|
|
4557
|
+
"ContactInformation is not recommended for Election, prefer using an"
|
|
4558
|
+
" ElectionAdministration."
|
|
4559
|
+
)
|
|
4560
|
+
|
|
4561
|
+
|
|
4562
|
+
class NoExtraElectionReportCollections(base.BaseRule):
|
|
4563
|
+
"""ElectionReports for voter information feeds should not have certain elements.
|
|
4564
|
+
|
|
4565
|
+
CommitteeCollection, GovernmentBodyCollection, OfficeCollection,
|
|
4566
|
+
OfficeHolderTenureCollection, PartyCollection, PersonCollection should all be
|
|
4567
|
+
excluded.
|
|
4568
|
+
"""
|
|
4569
|
+
|
|
4570
|
+
def elements(self):
|
|
4571
|
+
return ["ElectionReport"]
|
|
4572
|
+
|
|
4573
|
+
def check(self, element):
|
|
4574
|
+
if element.find("CommitteeCollection") is not None:
|
|
4575
|
+
raise loggers.ElectionError.from_message(
|
|
4576
|
+
"CommitteeCollection should not exist."
|
|
4577
|
+
)
|
|
4578
|
+
|
|
4579
|
+
if element.find("GovernmentBodyCollection") is not None:
|
|
4580
|
+
raise loggers.ElectionError.from_message(
|
|
4581
|
+
"GovernmentBodyCollection should not exist."
|
|
4582
|
+
)
|
|
4583
|
+
|
|
4584
|
+
if element.find("OfficeCollection") is not None:
|
|
4585
|
+
raise loggers.ElectionError.from_message(
|
|
4586
|
+
"OfficeCollection should not exist."
|
|
4587
|
+
)
|
|
4588
|
+
|
|
4589
|
+
if element.find("OfficeHolderTenureCollection") is not None:
|
|
4590
|
+
raise loggers.ElectionError.from_message(
|
|
4591
|
+
"OfficeHolderTenureCollection should not exist."
|
|
4592
|
+
)
|
|
4593
|
+
|
|
4594
|
+
if element.find("PartyCollection") is not None:
|
|
4595
|
+
raise loggers.ElectionError.from_message(
|
|
4596
|
+
"PartyCollection should not exist."
|
|
4597
|
+
)
|
|
4598
|
+
|
|
4599
|
+
if element.find("PersonCollection") is not None:
|
|
4600
|
+
raise loggers.ElectionError.from_message(
|
|
4601
|
+
"PersonCollection should not exist."
|
|
4602
|
+
)
|
|
4603
|
+
|
|
4604
|
+
|
|
4454
4605
|
class RuleSet(enum.Enum):
|
|
4455
4606
|
"""Names for sets of rules used to validate a particular feed type."""
|
|
4456
4607
|
|
|
@@ -4460,6 +4611,7 @@ class RuleSet(enum.Enum):
|
|
|
4460
4611
|
ELECTION_DATES = 4
|
|
4461
4612
|
ELECTION_RESULTS = 5
|
|
4462
4613
|
METADATA = 6
|
|
4614
|
+
VOTER_INFORMATION = 7
|
|
4463
4615
|
|
|
4464
4616
|
|
|
4465
4617
|
# To add new rules, create a new class, inherit the base rule,
|
|
@@ -4578,6 +4730,7 @@ ELECTION_RESULTS_RULES = ELECTION_RULES + (
|
|
|
4578
4730
|
OFFICEHOLDER_RULES = COMMON_RULES + (
|
|
4579
4731
|
# go/keep-sorted start
|
|
4580
4732
|
DateOfBirthIsInPast,
|
|
4733
|
+
OfficeHolderTenureTermDates,
|
|
4581
4734
|
OfficeSelectionMethodMatch,
|
|
4582
4735
|
OfficeTermDates,
|
|
4583
4736
|
PersonHasOffice,
|
|
@@ -4620,6 +4773,16 @@ METADATA_RULES = (
|
|
|
4620
4773
|
# go/keep-sorted end
|
|
4621
4774
|
)
|
|
4622
4775
|
|
|
4776
|
+
VOTER_INFORMATION_RULES = COMMON_RULES + (
|
|
4777
|
+
# go/keep-sorted start
|
|
4778
|
+
ElectoralCommissionCollectionExists,
|
|
4779
|
+
NoExtraElectionElements,
|
|
4780
|
+
NoExtraElectionReportCollections,
|
|
4781
|
+
VoterInformationCollectionExists,
|
|
4782
|
+
WarnOnElementsNotRecommendedForElection,
|
|
4783
|
+
# go/keep-sorted end
|
|
4784
|
+
)
|
|
4785
|
+
|
|
4623
4786
|
ALL_RULES = frozenset(
|
|
4624
4787
|
COMMON_RULES
|
|
4625
4788
|
+ ELECTION_RULES
|
|
@@ -4627,5 +4790,6 @@ ALL_RULES = frozenset(
|
|
|
4627
4790
|
+ OFFICEHOLDER_RULES
|
|
4628
4791
|
+ COMMITTEE_RULES
|
|
4629
4792
|
+ ELECTION_DATES_RULES
|
|
4793
|
+
+ VOTER_INFORMATION_RULES
|
|
4630
4794
|
+ METADATA_RULES
|
|
4631
4795
|
)
|
|
@@ -260,6 +260,8 @@ def filter_all_rules_using_user_arg(rules_allowlist, rule_set, rules_blocklist):
|
|
|
260
260
|
rule_names = [x.__name__ for x in rules.ELECTION_RESULTS_RULES]
|
|
261
261
|
elif rule_set == rules.RuleSet.METADATA:
|
|
262
262
|
rule_names = [x.__name__ for x in rules.METADATA_RULES]
|
|
263
|
+
elif rule_set == rules.RuleSet.VOTER_INFORMATION:
|
|
264
|
+
rule_names = [x.__name__ for x in rules.VOTER_INFORMATION_RULES]
|
|
263
265
|
else:
|
|
264
266
|
raise AssertionError("Invalid rule_set: " + rule_set)
|
|
265
267
|
if rules_blocklist:
|
civics_cdf_validator/version.py
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
civics_cdf_validator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
civics_cdf_validator/base.py,sha256=5zntjAdAA-bdwYOOStFgUle9LGX_ccelS--thMhUG9A,16954
|
|
3
|
+
civics_cdf_validator/gpunit_rules.py,sha256=Cg-qlOeVKTCQ5qUKYSxbVrrh3tdLt1X5Eg-7uyyR_SY,9487
|
|
4
|
+
civics_cdf_validator/loggers.py,sha256=LS6U9YWzu72V2Q0PwyE9xE03Ul392UAPRnrp2uRBMLA,6923
|
|
5
|
+
civics_cdf_validator/office_utils.py,sha256=aFxcFQZF2oBn0gPXt_kv1LxHGvwzABScT8X5yaYtVLw,2705
|
|
6
|
+
civics_cdf_validator/rules.py,sha256=Hn7qk3qLhidhQrb15iq1ZwN4XLJ4HVhY4VHBbNGY9u4,158898
|
|
7
|
+
civics_cdf_validator/stats.py,sha256=YNqv3Khkt_hJxCIunFBRSl23oXqu5gNjCmWMHFbAcSU,3819
|
|
8
|
+
civics_cdf_validator/validator.py,sha256=2nwlFfQ9AmpTaO5n5ekaO845Rm0JPjNF-Il6FJW_50k,12678
|
|
9
|
+
civics_cdf_validator/version.py,sha256=5WTRUaJiARj_r2UPhcMKa9SSvt8tRFJt6pCR0zFyGpc,189
|
|
10
|
+
civics_cdf_validator-1.49.dev11.dist-info/licenses/LICENSE-2.0.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
11
|
+
civics_cdf_validator-1.49.dev11.dist-info/METADATA,sha256=9odJ6J0QGbWLOgkjRweufw1myU5Tg_CX3ts5z_zLbCI,1009
|
|
12
|
+
civics_cdf_validator-1.49.dev11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
+
civics_cdf_validator-1.49.dev11.dist-info/entry_points.txt,sha256=QibgvtMOocwDi7fQ1emgMJ2lIlI41sW0__KIQdnVn90,77
|
|
14
|
+
civics_cdf_validator-1.49.dev11.dist-info/top_level.txt,sha256=PYT5Eclxbop5o6DJIcgs4IeU6Ds5jqyftjoFbgkkJYo,21
|
|
15
|
+
civics_cdf_validator-1.49.dev11.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
civics_cdf_validator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
civics_cdf_validator/base.py,sha256=5zntjAdAA-bdwYOOStFgUle9LGX_ccelS--thMhUG9A,16954
|
|
3
|
-
civics_cdf_validator/gpunit_rules.py,sha256=Cg-qlOeVKTCQ5qUKYSxbVrrh3tdLt1X5Eg-7uyyR_SY,9487
|
|
4
|
-
civics_cdf_validator/loggers.py,sha256=LS6U9YWzu72V2Q0PwyE9xE03Ul392UAPRnrp2uRBMLA,6923
|
|
5
|
-
civics_cdf_validator/office_utils.py,sha256=aFxcFQZF2oBn0gPXt_kv1LxHGvwzABScT8X5yaYtVLw,2705
|
|
6
|
-
civics_cdf_validator/rules.py,sha256=2Kjtp2sTltj71w_pQa9tAAvdwpxQJYa16NuHL4_wD4Y,153532
|
|
7
|
-
civics_cdf_validator/stats.py,sha256=YNqv3Khkt_hJxCIunFBRSl23oXqu5gNjCmWMHFbAcSU,3819
|
|
8
|
-
civics_cdf_validator/validator.py,sha256=SXPsYLu0G_JT2B03i24-yJFh41YzaSYecan4LQ_Xqm4,12553
|
|
9
|
-
civics_cdf_validator/version.py,sha256=XOa4xnNoLYpXsmOeZNQZtWSb-PQRJvLkfm36T1fBwBI,188
|
|
10
|
-
civics_cdf_validator-1.49.dev9.dist-info/licenses/LICENSE-2.0.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
11
|
-
civics_cdf_validator-1.49.dev9.dist-info/METADATA,sha256=3J9MrvSscqntXR4MFxPwzqHYDOU4TqLftz9_F_mgZrM,1008
|
|
12
|
-
civics_cdf_validator-1.49.dev9.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
|
13
|
-
civics_cdf_validator-1.49.dev9.dist-info/entry_points.txt,sha256=QibgvtMOocwDi7fQ1emgMJ2lIlI41sW0__KIQdnVn90,77
|
|
14
|
-
civics_cdf_validator-1.49.dev9.dist-info/top_level.txt,sha256=PYT5Eclxbop5o6DJIcgs4IeU6Ds5jqyftjoFbgkkJYo,21
|
|
15
|
-
civics_cdf_validator-1.49.dev9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{civics_cdf_validator-1.49.dev9.dist-info → civics_cdf_validator-1.49.dev11.dist-info}/top_level.txt
RENAMED
|
File without changes
|