__init__.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import collections
  2. import datetime
  3. import typing
  4. class Person:
  5. """
  6. >>> alice = Person('alice')
  7. >>> alice.name = 'Alice Test'
  8. >>> alice.birth_date = datetime.date(2019, 12, 23)
  9. >>> alice
  10. Person(alice, Alice Test, *2019-12-23)
  11. >>> str(alice)
  12. 'Alice Test (*2019-12-23)'
  13. >>> bob = Person('bob')
  14. >>> bob.name = 'Bob Test'
  15. >>> alice.father = bob
  16. """
  17. def __init__(self, identifier: str):
  18. self.__identifier: str = identifier
  19. self.name: typing.Optional[str] = None
  20. self.birth_date: typing.Optional[datetime.date] = None
  21. self.mother: typing.Optional["Person"] = None
  22. self.father: typing.Optional["Person"] = None
  23. @property
  24. def identifier(self) -> str:
  25. return self.__identifier
  26. def __hash__(self) -> int:
  27. return hash(self.__identifier)
  28. def __repr__(self) -> str:
  29. """
  30. >>> p = Person("max-mustermann")
  31. >>> repr(p)
  32. 'Person(max-mustermann)'
  33. >>> p.name = "Hr. Mustermann"
  34. >>> repr(p)
  35. 'Person(max-mustermann, Hr. Mustermann)'
  36. >>> p.name = "Max Mustermann"
  37. >>> repr(p)
  38. 'Person(max-mustermann, Max Mustermann)'
  39. >>> p.birth_date = datetime.date(1976, 2, 1)
  40. >>> repr(p)
  41. 'Person(max-mustermann, Max Mustermann, *1976-02-01)'
  42. """
  43. return "{}({})".format(
  44. type(self).__name__,
  45. ", ".join(
  46. filter(
  47. None,
  48. (
  49. self.identifier,
  50. self.name,
  51. "*" + self.birth_date.isoformat()
  52. if self.birth_date is not None
  53. else None,
  54. ),
  55. )
  56. ),
  57. )
  58. def __str__(self) -> str:
  59. """
  60. >>> p = Person("max-mustermann")
  61. >>> p.name = "Max Mustermann"
  62. >>> str(p)
  63. 'Max Mustermann'
  64. >>> p.birth_date = datetime.date(1976, 2, 1)
  65. >>> str(p)
  66. 'Max Mustermann (*1976-02-01)'
  67. """
  68. return (self.name or "unnamed") + (
  69. " (*{})".format(self.birth_date.isoformat()) if self.birth_date else ""
  70. )
  71. def __eq__(self, other: "Person") -> bool:
  72. """
  73. >>> maxl = Person("max")
  74. >>> maxl.name = "Max Mustermann"
  75. >>> maxl == Person("max")
  76. True
  77. >>> erika = Person("erika")
  78. >>> erika.name = "Max Mustermann"
  79. >>> maxl == erika
  80. False
  81. """
  82. return self.identifier == other.identifier
  83. def merge(self, person: "Person") -> None:
  84. """
  85. >>> p1 = Person("max")
  86. >>> p1.name = "Max Mustermann"
  87. >>> str(p1)
  88. 'Max Mustermann'
  89. >>> p2 = Person("max2")
  90. >>> p2.birth_date = datetime.date(1976, 2, 1)
  91. >>> p2.mother = Person("mother")
  92. >>> p2.father = Person("father")
  93. >>> str(p2)
  94. 'unnamed (*1976-02-01)'
  95. add attributes of p2 to p1:
  96. >>> p1.merge(p2)
  97. >>> str(p1)
  98. 'Max Mustermann (*1976-02-01)'
  99. >>> p1.mother, p1.father
  100. (Person(mother), Person(father))
  101. p2 is unchanged:
  102. >>> str(p2)
  103. 'unnamed (*1976-02-01)'
  104. """
  105. for attr in ["name", "birth_date", "mother", "father"]:
  106. if getattr(person, attr) is not None:
  107. setattr(self, attr, getattr(person, attr))
  108. @property
  109. def parents(self) -> typing.Tuple["Person"]:
  110. """
  111. >>> p = Person("max")
  112. >>> p.parents
  113. ()
  114. >>> p.mother = Person("mum")
  115. >>> p.parents
  116. (Person(mum),)
  117. >>> p.father = Person("dad")
  118. >>> p.parents
  119. (Person(mum), Person(dad))
  120. >>> p.mother = None
  121. >>> p.parents
  122. (Person(dad),)
  123. """
  124. return tuple(filter(None, [self.mother, self.father]))
  125. class PersonCollection:
  126. """
  127. >>> bob = Person('bob')
  128. >>> bob.name = 'Bob Test'
  129. >>> alice = Person('alice')
  130. >>> alice.name = 'Alice Test'
  131. >>> alice.father = bob
  132. >>> collection = PersonCollection()
  133. >>> collection.add_person(alice)
  134. Person(alice, Alice Test)
  135. >>> for person in collection:
  136. ... print(person.name)
  137. Bob Test
  138. Alice Test
  139. """
  140. def __init__(self):
  141. self._persons = {}
  142. self.__children = collections.defaultdict(set)
  143. self.__it = None
  144. def __getitem__(self, identifier: str) -> Person:
  145. """
  146. >>> c = PersonCollection()
  147. >>> c.add_person(Person("alice"))
  148. Person(alice)
  149. >>> c["alice"]
  150. Person(alice)
  151. """
  152. return self._persons[identifier]
  153. def add_person(self, person: Person) -> Person:
  154. """
  155. >>> c = PersonCollection()
  156. >>> c.add_person(Person("alice"))
  157. Person(alice)
  158. >>> c.add_person(Person("bob"))
  159. Person(bob)
  160. >>> c["bob"]
  161. Person(bob)
  162. >>> bob = Person("bob")
  163. >>> bob.birth_date = datetime.date(2010, 2, 3)
  164. >>> bob.mother = Person("bob-mum")
  165. >>> bob.father = Person("bob-dad")
  166. >>> c.add_person(bob)
  167. Person(bob, *2010-02-03)
  168. >>> list(c)
  169. [Person(alice), Person(bob, *2010-02-03), Person(bob-mum), Person(bob-dad)]
  170. """
  171. if person.mother is not None:
  172. person.mother = self.add_person(person.mother)
  173. self.__children[person.mother.identifier].add(person)
  174. if person.father is not None:
  175. person.father = self.add_person(person.father)
  176. self.__children[person.father.identifier].add(person)
  177. if person.identifier not in self._persons:
  178. self._persons[person.identifier] = person
  179. return person
  180. existing_person = self._persons[person.identifier]
  181. existing_person.merge(person)
  182. return existing_person
  183. def get_children(self, parent: typing.Union[Person, str]) -> typing.Set[Person]:
  184. """
  185. >>> c = PersonCollection()
  186. >>> alice = Person('alice')
  187. >>> alice.father = Person('bob')
  188. >>> c.add_person(alice)
  189. Person(alice)
  190. >>> c.get_children(alice)
  191. set()
  192. >>> c.get_children('bob')
  193. {Person(alice)}
  194. >>> carol = Person('carol')
  195. >>> carol.father = c['bob']
  196. >>> c.add_person(carol)
  197. Person(carol)
  198. >>> sorted(c.get_children('bob'), key=lambda p: p.identifier)
  199. [Person(alice), Person(carol)]
  200. does not support change / removal of parents:
  201. >>> carol.father = Person('other-bob')
  202. >>> c.add_person(carol)
  203. Person(carol)
  204. >>> c.get_children('other-bob')
  205. {Person(carol)}
  206. >>> sorted(c.get_children('bob'), key=lambda p: p.identifier)
  207. [Person(alice), Person(carol)]
  208. """
  209. if isinstance(parent, str):
  210. return self.__children[parent]
  211. return self.get_children(parent.identifier)
  212. def get_coparents(
  213. self, parent: typing.Union[Person, str]
  214. ) -> typing.Iterator[Person]:
  215. """
  216. >>> c = PersonCollection()
  217. >>> alice = Person('alice')
  218. >>> alice.father = Person('bob')
  219. >>> c.add_person(alice)
  220. Person(alice)
  221. >>> list(c.get_coparents('bob'))
  222. []
  223. >>> alice.mother = Person('carol')
  224. >>> c.add_person(alice)
  225. Person(alice)
  226. >>> list(c.get_coparents('bob'))
  227. [Person(carol)]
  228. >>> list(c.get_coparents(c['carol']))
  229. [Person(bob)]
  230. """
  231. if isinstance(parent, Person):
  232. for coparent in self.get_coparents(parent.identifier):
  233. yield coparent
  234. return
  235. for child in self.get_children(parent):
  236. for coparent in child.parents:
  237. if coparent.identifier != parent:
  238. yield coparent
  239. def __iter__(self) -> "PersonCollection":
  240. """
  241. >>> c = PersonCollection()
  242. >>> for identifier in ['alice', 'bob', 'charlie']:
  243. ... c.add_person(Person(identifier))
  244. Person(alice)
  245. Person(bob)
  246. Person(charlie)
  247. >>> list(c)
  248. [Person(alice), Person(bob), Person(charlie)]
  249. """
  250. self.__it = iter(self._persons.values())
  251. return self
  252. def __next__(self) -> Person:
  253. return next(self.__it)