validator.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. '''
  2. @author: olivier.massot, sept. 2018
  3. '''
  4. import logging
  5. from qgis.core import QgsProject
  6. from core.cerberus_extend import CerberusErrorHandler, \
  7. _translate_messages, ExtendedValidator
  8. from core.model import QgsModel
  9. from core.validation_errors import CRITICAL, DataError, GeomTypeError, BoundingBoxError, \
  10. MissingLayer, WrongSrid
  11. logger = logging.getLogger("mncheck")
  12. class ValidatorInterruption(BaseException):
  13. pass
  14. class Checkpoint():
  15. def __init__(self, name, valid=True):
  16. self.name = name
  17. self.valid = valid
  18. class BaseValidator():
  19. schema_name = ""
  20. models = {}
  21. dataset = {}
  22. def __init__(self):
  23. self.valid = True
  24. self.checkpoints = []
  25. self.errors = []
  26. self._current_checkpoint_valid = True
  27. self.dt = 0
  28. def checkpoint(self, title):
  29. self.checkpoints.append(Checkpoint(title, self._current_checkpoint_valid))
  30. self._current_checkpoint_valid = True
  31. if self.errors:
  32. self.valid = False
  33. if self.critical_happened():
  34. raise ValidatorInterruption()
  35. def critical_happened(self):
  36. return any([err.level == CRITICAL for err in self.errors])
  37. def log_error(self, validation_error):
  38. self._current_checkpoint_valid = False
  39. self.errors.append(validation_error)
  40. @classmethod
  41. def submit(cls, report_name=""):
  42. validator = cls()
  43. try:
  44. validator.validate()
  45. except ValidatorInterruption:
  46. pass
  47. report = validator.build_report(validator.schema_name, report_name or "(nom inconnu)")
  48. return report
  49. def validate(self):
  50. # Chargement des données en mémoire
  51. self._load_layers()
  52. self.checkpoint("Chargement des données")
  53. # Controle la structure des données (champs, formats et types)
  54. self._structure_validation()
  55. self.checkpoint("Contrôle de la structure des données")
  56. # Validation technique
  57. try:
  58. self._technical_validation()
  59. self.checkpoint("Validation Métier")
  60. except ValidatorInterruption:
  61. raise
  62. except:
  63. self.checkpoint("Validation Métier [interrompu]")
  64. def _load_layers(self):
  65. for model in self.models:
  66. layername = model.layername
  67. layer = next((l for l in QgsProject.instance().mapLayers().values() if l.name().lower() == layername))
  68. if not layer:
  69. self.log_error(MissingLayer("Couche manquante: '{}'".format(layername)))
  70. continue
  71. self.dataset[model] = []
  72. if layer.crs().authid() != model.crs:
  73. self.log_error(WrongSrid("Mauvaise projection: {} (attendu: {})".format(layer.crs().authid(), model.crs)))
  74. for feature in layer.getFeatures():
  75. item = model(feature)
  76. self.dataset[model].append(item)
  77. def _structure_validation(self):
  78. for model in self.models:
  79. v = ExtendedValidator(model.schema, purge_unknown=True, error_handler=CerberusErrorHandler, require_all=True)
  80. xmin, ymin, xmax, ymax = model.bounding_box
  81. for item in self.dataset[model]:
  82. # geom valid
  83. if not item.is_geometry_valid():
  84. self.log_error(GeomTypeError("La géométrie de l'objet est invalide".format(), layername=model.layername, field="geom"))
  85. # geom type
  86. if item.get_geom_type() != model.geom_type:
  87. self.log_error(GeomTypeError("Type de géométrie invalide: {} (attendu: {})".format(item.geom_name, QgsModel.GEOM_NAMES[model.geom_type]), layername=model.layername, field="geom"))
  88. # bounding box
  89. x1, y1, x2, y2 = item.get_bounding_box()
  90. if any(x < xmin or x > xmax for x in (x1, x2)) or \
  91. any(y < ymin or y > ymax for y in (y1, y2)):
  92. self.log_error(BoundingBoxError("Situé hors de l'emprise autorisée", layername=model.layername, field="geom"))
  93. v.validate(item.__dict__)
  94. for field, verrors in v.errors.items():
  95. for err in verrors:
  96. self.log_error(DataError(_translate_messages(err), layername=model.layername, field=field))
  97. @classmethod
  98. def _technical_validation(cls):
  99. raise NotImplementedError()
  100. def build_report(self, schema, layername):
  101. report = {}
  102. report["schema"] = schema
  103. report["reportname"] = ""
  104. report["checkpoints"] = [{"name": chk.name, "valid": chk.valid} for chk in self.checkpoints]
  105. report["errors"] = {}
  106. for err in self.errors:
  107. if not err.name in report["errors"]:
  108. report["errors"][err.name] = {"help": err.help, "order_": err.order_, "list": []}
  109. err_report = {"layername": err.layername or "-",
  110. "field": err.field or "-",
  111. "message": err.message}
  112. if err_report not in report["errors"][err.name]["list"]:
  113. report["errors"][err.name]["list"].append(err_report)
  114. return report