model.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. '''
  2. @author: olivier.massot, févr. 2018
  3. '''
  4. import csv
  5. from datetime import datetime
  6. import logging
  7. import dateutil.parser
  8. from core.sqlformatter import SqlFormatter
  9. logger = logging.getLogger("model")
  10. Sql = SqlFormatter()
  11. csv.register_dialect('tsv', delimiter='\t', quotechar='', quoting=csv.QUOTE_NONE)
  12. class Model():
  13. _mapping = {}
  14. def __setattr__(self, name, value):
  15. try:
  16. val, type_ = value
  17. if not type_ in (int, float, bool, str, datetime):
  18. raise TypeError("Type de donnée invalide pour une propriété de 'Model' ('{}')".format(type(val)))
  19. self.__class__._mapping[name] = type_
  20. except (TypeError, ValueError):
  21. val = value
  22. if val is None:
  23. super().__setattr__(name, val)
  24. return
  25. try:
  26. type_ = self.__class__._mapping[name]
  27. except KeyError:
  28. super().__setattr__(name, val)
  29. return
  30. val = Model._cast(val, type_)
  31. super().__setattr__(name, val)
  32. @staticmethod
  33. def _cast(value, type_):
  34. if value is None:
  35. return value
  36. if type_ == datetime:
  37. if type(value) is str:
  38. return dateutil.parser.parse(value)
  39. elif type(value) is datetime:
  40. return value
  41. else:
  42. raise ValueError("'{}' ne peut pas être converti en date".format(value))
  43. if type_ is bool and value in ("True", "False"):
  44. return (value == "True")
  45. else:
  46. return type_(value)
  47. @property
  48. def _fields(self):
  49. return list(self.__dict__.keys())
  50. @property
  51. def data(self):
  52. return self.__dict__
  53. def __repr__(self):
  54. return "<{} => {}>".format(self.__class__.__name__, ",".join(["{}={}".format(field, value) for field, value in self.__dict__.items()]))
  55. @classmethod
  56. def from_dict(cls, data):
  57. """ Retourne un objet à partir d'un dictionnaire de données """
  58. model = cls()
  59. for key, value in data.items():
  60. setattr(model, key, value)
  61. return model
  62. @classmethod
  63. def _parse(cls, field, value):
  64. if value == 'None':
  65. value = None
  66. try:
  67. return Model._cast(value, cls._mapping[field])
  68. except KeyError:
  69. return value
  70. # Fonctions CSV
  71. def dump_to_csv(self, path):
  72. """ Ajoute les données du modèle au format CSV dans le fichier spécifié en paramètre.
  73. Créé le fichier s'il n'existe pas, avec une ligne d'en-tête """
  74. if not path.exists():
  75. logger.debug("Génère le fichier %s", path)
  76. with open(path, 'w+', newline='') as f:
  77. writer = csv.writer(f, 'tsv')
  78. writer.writerow(self._fields)
  79. with open(path, "a", newline='') as f:
  80. writer = csv.writer(f, 'tsv')
  81. writer.writerow([str(getattr(self, field)).replace("\t", " ") for field in self._fields])
  82. @classmethod
  83. def load_csv(cls, path):
  84. """ parcourt les lignes du fichier csv et renvoie chaque ligne sous forme d'un objet Model
  85. ATTENTION: chaque propriété dont le type n'est pas précisé dans _mapping aura le type 'string'
  86. """
  87. with open(path) as f:
  88. reader = csv.reader(f, 'tsv')
  89. fields = next(reader)
  90. for row in csv.reader(f, 'tsv'):
  91. data = {key: cls._parse(key, value) for key, value in zip(fields, row)}
  92. yield(cls.from_dict(data))