__init__.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. import datetime
  2. import ioex.calcex
  3. import locale
  4. import pytz
  5. import re
  6. import yaml
  7. class _Object(object):
  8. def __eq__(self, other):
  9. return type(self) == type(other) and vars(self) == vars(other)
  10. def __ne__(self, other):
  11. return not (self == other)
  12. class _YamlInitConstructor(yaml.YAMLObject):
  13. @classmethod
  14. def from_yaml(cls, loader, node):
  15. return cls(**loader.construct_mapping(node, deep=True))
  16. # return cls(**{
  17. # k: unicode(v) if isinstance(v, str) else v
  18. # for (k, v) in loader.construct_mapping(node, deep = True).items()
  19. # })
  20. @classmethod
  21. def register_yaml_constructor(cls, loader, tag=None):
  22. loader.add_constructor(
  23. cls.yaml_tag if tag is None else tag,
  24. cls.from_yaml,
  25. )
  26. class Sum(ioex.calcex.Figure):
  27. currency_symbol_map = {
  28. '€': 'EUR',
  29. 'US$': 'USD',
  30. '¥': 'CNY',
  31. }
  32. yaml_tag = u'!sum'
  33. def __init__(self, value=None, currency=None, unit=None):
  34. if not currency is None and not unit is None:
  35. raise ValueError('missing currency')
  36. else:
  37. unit = currency if currency else unit
  38. super(Sum, self).__init__(
  39. value=value,
  40. unit=currency if currency else unit,
  41. )
  42. def get_value(self):
  43. return super(Sum, self).get_value()
  44. def set_value(self, value):
  45. assert type(value) is float
  46. super(Sum, self).set_value(value)
  47. """ use property() instead of decorator to enable overriding """
  48. value = property(get_value, set_value)
  49. def get_unit(self):
  50. return super(Sum, self).get_unit()
  51. def set_unit(self, currency):
  52. currency = Sum.currency_symbol_map.get(currency, currency)
  53. assert type(currency) is str
  54. super(Sum, self).set_unit(currency)
  55. """ use property() instead of decorator to enable overriding """
  56. unit = property(get_unit, set_unit)
  57. currency = property(get_unit, set_unit)
  58. value_regex = r"-?\d+([\.,]\d{2})?"
  59. currency_regex = r"[^\d\s-]+"
  60. @staticmethod
  61. def parse_text(text):
  62. match = re.search(
  63. r'^\$?(?P<value>{}) (?P<curr>{})$'.format(
  64. Sum.value_regex, Sum.currency_regex),
  65. text,
  66. re.UNICODE,
  67. )
  68. if not match:
  69. match = re.search(
  70. r'^(?P<curr>{}) ?(?P<value>{})$'.format(
  71. Sum.currency_regex, Sum.value_regex),
  72. text,
  73. re.UNICODE,
  74. )
  75. assert not match is None, text
  76. attr = match.groupdict()
  77. return Sum(
  78. value=locale.atof(attr['value']),
  79. currency=attr['curr'],
  80. )
  81. class Order(_Object, _YamlInitConstructor):
  82. yaml_tag = u'!order'
  83. def __init__(self, platform, order_id, order_date,
  84. customer_id=None,
  85. items=None,
  86. discounts=None,
  87. ):
  88. assert type(platform) is str
  89. self.platform = platform
  90. if type(order_id) in [int]:
  91. order_id = str(order_id)
  92. assert type(order_id) is str
  93. self.order_id = order_id
  94. assert type(order_date) in [datetime.date, datetime.datetime]
  95. if type(order_date) is datetime.datetime and order_date.tzinfo:
  96. order_date = order_date.astimezone(pytz.utc)
  97. self.order_date = order_date
  98. if customer_id is not None:
  99. assert type(customer_id) is str
  100. self.customer_id = customer_id
  101. if items is None:
  102. self.items = []
  103. else:
  104. assert type(items) is list
  105. assert all([isinstance(i, Item) for i in items])
  106. self.items = items
  107. if discounts is None:
  108. self.discounts = []
  109. else:
  110. assert type(discounts) is list
  111. assert all([isinstance(d, Discount) for d in discounts])
  112. self.discounts = discounts
  113. class Distance(ioex.calcex.Figure):
  114. yaml_tag = u'!distance'
  115. def get_value(self):
  116. return super(Distance, self).get_value()
  117. def set_value(self, value):
  118. assert type(value) is float
  119. super(Distance, self).set_value(value)
  120. """ use property() instead of decorator to enable overriding """
  121. value = property(get_value, set_value)
  122. @property
  123. def metres(self):
  124. if self.unit == 'm':
  125. return Distance(self.value, 'm')
  126. elif self.unit == 'km':
  127. return Distance(self.value * 1000, 'm')
  128. else:
  129. raise Exception()
  130. class Discount(_Object, _YamlInitConstructor):
  131. yaml_tag = u'!discount'
  132. def __init__(self, name=None, amount=None):
  133. assert type(name) is str
  134. self.name = name
  135. assert type(amount) is Sum
  136. assert amount.value >= 0
  137. self.amount = amount
  138. class Item(_Object, _YamlInitConstructor):
  139. yaml_tag = u'!item'
  140. def __init__(self,
  141. name=None,
  142. price_brutto=None,
  143. ):
  144. if not name is None:
  145. assert type(name) is str
  146. self.name = name
  147. assert type(price_brutto) is Sum
  148. self.price_brutto = price_brutto
  149. class Campaign(_Object, _YamlInitConstructor):
  150. yaml_tag = u'!campaign'
  151. def __init__(self,
  152. end=None,
  153. founder=None,
  154. name=None,
  155. website_url=None,
  156. ):
  157. assert type(name) is str
  158. self.name = name
  159. assert type(founder) is str
  160. self.founder = founder
  161. if not end is None:
  162. assert type(end) is datetime.datetime
  163. assert not end.tzinfo is None, '%r' % end
  164. self.end = end
  165. if not website_url is None:
  166. assert type(website_url) is str
  167. self.website_url = website_url
  168. class Pledge(Item):
  169. yaml_tag = u'!pledge'
  170. def __init__(self,
  171. campaign=None,
  172. reward=None,
  173. **kwargs
  174. ):
  175. super(Pledge, self).__init__(**kwargs)
  176. assert type(campaign) is Campaign
  177. self.campaign = campaign
  178. if not reward is None:
  179. assert type(reward) is str
  180. self.reward = reward
  181. class Contribution(Item):
  182. yaml_tag = u'!contribution'
  183. def __init__(self,
  184. campaign=None,
  185. reward=None,
  186. **kwargs
  187. ):
  188. super(Contribution, self).__init__(**kwargs)
  189. assert type(campaign) is Campaign
  190. self.campaign = campaign
  191. if not reward is None:
  192. assert type(reward) is str
  193. self.reward = reward
  194. class Article(Item):
  195. yaml_tag = u'!article'
  196. def __init__(self,
  197. authors=None,
  198. color=None,
  199. delivery_date=None,
  200. depth=None,
  201. features=None,
  202. height=None,
  203. maximum_load=None,
  204. option=None,
  205. product_id=None,
  206. quantity=None,
  207. reseller=None,
  208. shipper=None,
  209. size=None,
  210. state=None,
  211. width=None,
  212. **kwargs
  213. ):
  214. super(Article, self).__init__(**kwargs)
  215. assert not self.name is None
  216. assert type(quantity) is int
  217. self.quantity = quantity
  218. if authors is not None:
  219. assert type(authors) is list
  220. self.authors = authors
  221. if state is not None:
  222. assert type(state) is str
  223. self.state = state
  224. if reseller is not None:
  225. assert type(reseller) is str
  226. self.reseller = reseller
  227. if shipper is not None:
  228. assert type(shipper) is str
  229. self.shipper = shipper
  230. if product_id is not None:
  231. if type(product_id) in [int]:
  232. product_id = str(product_id)
  233. assert type(product_id) is str
  234. self.product_id = product_id
  235. if option is not None:
  236. assert type(option) is str
  237. self.option = option
  238. if color is not None:
  239. assert type(color) is str
  240. self.color = color
  241. if size is not None:
  242. assert type(size) is str
  243. self.size = size
  244. if width is not None:
  245. assert type(width) is Distance
  246. self.width = width
  247. if depth is not None:
  248. assert type(depth) is Distance
  249. self.depth = depth
  250. if height is not None:
  251. assert type(height) is Distance
  252. self.height = height
  253. if maximum_load is not None:
  254. assert type(maximum_load) is ioex.calcex.Figure, type(maximum_load)
  255. self.maximum_load = maximum_load
  256. if features is not None:
  257. assert type(features) is str
  258. self.features = features
  259. assert delivery_date is None or type(delivery_date) is datetime.date
  260. self.delivery_date = delivery_date
  261. class Service(Item):
  262. yaml_tag = u'!service'
  263. def __init__(self,
  264. duration=None,
  265. **kwargs
  266. ):
  267. super(Service, self).__init__(**kwargs)
  268. if duration is not None:
  269. assert type(duration) is ioex.datetimeex.Duration
  270. self.duration = duration
  271. class Transportation(Item):
  272. yaml_tag = u'!transportation'
  273. def __init__(self,
  274. departure_point=None,
  275. destination_point=None,
  276. distance=None,
  277. estimated_arrival_time=None,
  278. passenger=None,
  279. route_map=None,
  280. ticket_url=None,
  281. valid_from=None,
  282. valid_until=None,
  283. **kwargs
  284. ):
  285. super(Transportation, self).__init__(**kwargs)
  286. if departure_point is not None:
  287. assert type(departure_point) is str
  288. self.departure_point = departure_point
  289. if destination_point is not None:
  290. assert type(destination_point) is str
  291. self.destination_point = destination_point
  292. if distance is not None:
  293. assert type(distance) is Distance
  294. self.distance = distance
  295. if route_map is not None:
  296. assert type(route_map) is str
  297. self.route_map = route_map
  298. if passenger is not None:
  299. assert type(passenger) is Person
  300. self.passenger = passenger
  301. if valid_from is not None:
  302. assert type(valid_from) is datetime.datetime
  303. assert not valid_from.tzinfo is None
  304. self.valid_from = valid_from
  305. if valid_until is not None:
  306. assert type(valid_until) is datetime.datetime
  307. assert not valid_until.tzinfo is None
  308. self.valid_until = valid_until
  309. if ticket_url is not None:
  310. assert type(ticket_url) is str
  311. self.ticket_url = ticket_url
  312. if estimated_arrival_time is not None:
  313. assert type(estimated_arrival_time) is ioex.datetimeex.Period
  314. assert not estimated_arrival_time.start.tzinfo is None
  315. assert not estimated_arrival_time.end.tzinfo is None
  316. self.estimated_arrival_time = estimated_arrival_time
  317. class Shipping(Transportation):
  318. yaml_tag = u'!shipping'
  319. def __init__(self,
  320. **kwargs
  321. ):
  322. super(Shipping, self).__init__(**kwargs)
  323. class TaxiRide(Transportation):
  324. yaml_tag = u'!taxi-ride'
  325. def __init__(self,
  326. arrival_time=None,
  327. departure_time=None,
  328. driver=None,
  329. name=None,
  330. **kwargs
  331. ):
  332. if name is None:
  333. name = u'Taxi Ride'
  334. super(TaxiRide, self).__init__(name=name, **kwargs)
  335. assert type(driver) is str
  336. self.driver = driver
  337. assert arrival_time is None or type(arrival_time) is datetime.datetime
  338. self.arrival_time = arrival_time
  339. assert departure_time is None or type(
  340. departure_time) is datetime.datetime
  341. self.departure_time = departure_time
  342. class Person(_Object, _YamlInitConstructor):
  343. yaml_tag = u'!person'
  344. def __init__(self, first_name=None, last_name=None):
  345. self.first_name = first_name
  346. self.last_name = last_name
  347. @property
  348. def first_name(self):
  349. return self._first_name
  350. @first_name.setter
  351. def first_name(self, first_name):
  352. assert first_name is None or type(first_name) is str
  353. self._first_name = first_name
  354. @property
  355. def last_name(self):
  356. return self._last_name
  357. @last_name.setter
  358. def last_name(self, last_name):
  359. assert last_name is None or type(last_name) is str
  360. self._last_name = last_name
  361. @classmethod
  362. def to_yaml(cls, dumper, person):
  363. return dumper.represent_mapping(cls.yaml_tag, {
  364. 'first_name': person.first_name,
  365. 'last_name': person.last_name,
  366. })
  367. def __repr__(self):
  368. return self.__class__.__name__ + '(%s)' % ', '.join([
  369. '%s=%r' % (k, v) for k, v in vars(self).items()
  370. ])