civics-cdf-validator 1.60.dev1__tar.gz → 1.60.dev2__tar.gz

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.
Files changed (22) hide show
  1. {civics_cdf_validator-1.60.dev1/civics_cdf_validator.egg-info → civics_cdf_validator-1.60.dev2}/PKG-INFO +1 -1
  2. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2/civics_cdf_validator.egg-info}/PKG-INFO +1 -1
  3. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/rules.py +63 -5
  4. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/version.py +1 -1
  5. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/CONTRIBUTING.md +0 -0
  6. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/LICENSE-2.0.txt +0 -0
  7. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/MANIFEST.in +0 -0
  8. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/README.md +0 -0
  9. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/__init__.py +0 -0
  10. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/base.py +0 -0
  11. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/civics_cdf_validator.egg-info/SOURCES.txt +0 -0
  12. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/civics_cdf_validator.egg-info/dependency_links.txt +0 -0
  13. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/civics_cdf_validator.egg-info/entry_points.txt +0 -0
  14. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/civics_cdf_validator.egg-info/requires.txt +0 -0
  15. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/civics_cdf_validator.egg-info/top_level.txt +0 -0
  16. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/gpunit_rules.py +0 -0
  17. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/loggers.py +0 -0
  18. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/office_utils.py +0 -0
  19. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/setup.cfg +0 -0
  20. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/setup.py +0 -0
  21. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/stats.py +0 -0
  22. {civics_cdf_validator-1.60.dev1 → civics_cdf_validator-1.60.dev2}/validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: civics_cdf_validator
3
- Version: 1.60.dev1
3
+ Version: 1.60.dev2
4
4
  Summary: Checks if an election feed follows best practices
5
5
  Home-page: https://github.com/google/civics_cdf_validator
6
6
  Author: Google Civics
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: civics_cdf_validator
3
- Version: 1.60.dev1
3
+ Version: 1.60.dev2
4
4
  Summary: Checks if an election feed follows best practices
5
5
  Home-page: https://github.com/google/civics_cdf_validator
6
6
  Author: Google Civics
@@ -1714,6 +1714,65 @@ class PersonsMissingPartyData(base.BaseRule):
1714
1714
  )
1715
1715
 
1716
1716
 
1717
+ class OnlyOneCandidateImagePerPerson(base.BaseRule):
1718
+ """Ensure only one candidate-image is provided per Person."""
1719
+
1720
+ def elements(self):
1721
+ return ["Person"]
1722
+
1723
+ def check(self, element):
1724
+ image_uris = element.findall("ImageUri")
1725
+ candidate_images = []
1726
+ for image_uri in image_uris:
1727
+ annotation = image_uri.get("Annotation", "").strip()
1728
+ if annotation == "candidate-image":
1729
+ candidate_images.append(image_uri)
1730
+
1731
+ if len(candidate_images) > 1:
1732
+ raise loggers.ElectionError.from_message(
1733
+ "Person has {} ImageUri fields annotated as 'candidate-image'."
1734
+ " Must have at most one.".format(len(candidate_images)),
1735
+ candidate_images,
1736
+ )
1737
+
1738
+
1739
+ class UniqueCandidateImageUris(base.TreeRule):
1740
+ """Check that candidate-image URIs are unique to a person."""
1741
+
1742
+ def check(self):
1743
+ root = self.election_tree.getroot()
1744
+ if root is None:
1745
+ return
1746
+
1747
+ persons_by_candidate_image_uri = collections.defaultdict(list)
1748
+ person_collection = root.find("PersonCollection")
1749
+ if person_collection is None:
1750
+ return
1751
+ for person in person_collection.findall("Person"):
1752
+ for image_uri_element in person.findall("ImageUri"):
1753
+ if not element_has_text(image_uri_element):
1754
+ continue
1755
+ annotation = image_uri_element.get("Annotation", "").strip()
1756
+ if annotation == "candidate-image":
1757
+ image_uri = image_uri_element.text.strip()
1758
+ if image_uri:
1759
+ persons_by_candidate_image_uri[image_uri].append(person)
1760
+
1761
+ error_log = []
1762
+ for image_uri, persons in persons_by_candidate_image_uri.items():
1763
+ if len(persons) > 1:
1764
+ person_ids = sorted(person.get("objectId") for person in persons)
1765
+ error_log.append(
1766
+ loggers.LogEntry(
1767
+ f"Candidate image URI '{image_uri}' is shared by multiple"
1768
+ f" people: [{', '.join(person_ids)}].",
1769
+ persons,
1770
+ )
1771
+ )
1772
+ if error_log:
1773
+ raise loggers.ElectionError(error_log)
1774
+
1775
+
1717
1776
  class AllCaps(base.BaseRule):
1718
1777
  """Name elements should not be in all uppercase.
1719
1778
 
@@ -2364,11 +2423,8 @@ class ValidURIAnnotation(base.BaseRule):
2364
2423
  "URI {} is missing annotation.".format(ascii_url), [uri]
2365
2424
  )
2366
2425
 
2367
- # Skip platform checks for image or office contact form annotations.
2368
- if (
2369
- re.search(r"candidate-image", annotation)
2370
- or annotation == "office-contact_form"
2371
- ):
2426
+ # Skip platform checks for office contact form annotations.
2427
+ if annotation == "office-contact_form":
2372
2428
  continue
2373
2429
 
2374
2430
  ann_elements = annotation.split("-")
@@ -4811,6 +4867,7 @@ COMMON_RULES = (
4811
4867
  OfficesHaveJurisdictionID,
4812
4868
  OfficesHaveValidOfficeLevel,
4813
4869
  OfficesHaveValidOfficeRole,
4870
+ OnlyOneCandidateImagePerPerson,
4814
4871
  OptionalAndEmpty,
4815
4872
  OtherType,
4816
4873
  PartyLeadershipMustExist,
@@ -4820,6 +4877,7 @@ COMMON_RULES = (
4820
4877
  PersonsMissingPartyData,
4821
4878
  Schema,
4822
4879
  URIValidator,
4880
+ UniqueCandidateImageUris,
4823
4881
  UniqueLabel,
4824
4882
  UniqueStableID,
4825
4883
  UniqueURIPerAnnotationCategory,
@@ -5,4 +5,4 @@ No dependencies should be added to this module.
5
5
  See https://packaging.python.org/guides/single-sourcing-package-version/
6
6
  """
7
7
 
8
- __version__ = '1.60.dev1'
8
+ __version__ = '1.60.dev2'