model.py 3.3 KB

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