Test Failed
Push — master ( f337c2...a26c02 )
by Oliver
02:03
created

person.MoP.__post_init__()   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nop 1
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
"""
4
A set of dataclasses concerning roles of persons and their particulars.
5
"""
6
import datetime
7
import os
8
import sys
9
from dataclasses import dataclass, field
10
from typing import List, Optional, Tuple
11
12
from gender_guesser import detector as sex  # type: ignore
13
14
PACKAGE_PARENT = ".."
15
SCRIPT_DIR = os.path.dirname(
16
    os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__)))
17
)  # isort:skip # noqa # pylint: disable=wrong-import-position
18
sys.path.append(
19
    os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT))
20
)  # isort: skip # noqa # pylint: disable=wrong-import-position
21
22
from src.resources.constants import GERMAN_PARTIES  # type: ignore  # noqa
23
from src.resources.constants import PEER_PREPOSITIONS  # type: ignore # noqa
24
from src.resources.constants import PEERTITLES  # type: ignore # noqa
25
from src.resources.helpers import (  # type: ignore # noqa; type: ignore # noqa; type: ignore  # noqa; type: ignore # noqa
26
    AttrDisplay,
27
    NotInRange,
28
    Party,
29
    TooManyFirstNames,
30
)
31
32
33
@dataclass
34
class _Name_default:
35
    middle_name_1: Optional[str] = field(default=None)
36
    middle_name_2: Optional[str] = field(default=None)
37
    maiden_name: Optional[str] = field(default=None)
38
    divorcée: Optional[str] = field(default=None)
39
40
41
@dataclass
42
class _Name_base:
43
    first_name: str
44
    last_name: str
45
46
47
@dataclass
48
class Name(_Name_default, _Name_base, AttrDisplay):
49
50
    """
51
    The most basic part to describe a person.
52
    To add more middle names, dataclass _Name_default has to be given further
53
    middle_name attributes. Since this project currently focusses on German
54
    politicians, the limit of three given names is preserved.
55
    """
56
57
    def __post_init__(self):
58
        """
59
        In case a Name instance is initialized with all first names in one
60
        string, __post_init__ will take care of this and assign each first
61
        name its attribute. Also it will raise TooManyFirstNames if more than
62
        three first names are given.
63
        """
64
        first_names = self.first_name.split(" ")
65
        self.first_name = first_names[0]
66
        if len(first_names) == 2:
67
            self.middle_name_1 = first_names[1]
68
        elif len(first_names) == 3:
69
            self.middle_name_1 = first_names[1]
70
            self.middle_name_2 = first_names[-1]
71
        elif len(first_names) > 3:
72
            print(first_names)
73
            raise TooManyFirstNames("There are more than three first names!")
74
75
76
@dataclass
77
class _Peertitle_default:
78
    peer_title: Optional[str] = field(default=None)
79
    peer_preposition: Optional[str] = field(default=None)
80
81
    def nobility_title(self) -> None:
82
        if self.peer_title is not None:
83
            title = self.peer_title
84
            self.peer_title, self.peer_preposition = self.title_fix(title)
85
86
    def title_fix(self, title) -> Tuple[str, str]:
87
        titles = title.split(" ")
88
        title_tmp = ""
89
        preposition_tmp = ""
90
        for prep in titles:
91
            if prep.lower() in PEER_PREPOSITIONS:
92
                preposition_tmp = preposition_tmp + prep.lower() + " "
93
            elif prep in PEERTITLES:
94
                title_tmp = title_tmp + prep + " "
95
        peer_preposition = preposition_tmp.strip()
96
        peer_title = title_tmp.strip()
97
98
        return peer_title, peer_preposition
99
100
101
@dataclass
102
class Noble(_Peertitle_default, Name, AttrDisplay):
103
    def __post_init__(self):
104
        """Initialize names and titles."""
105
        Name.__post_init__(self)
106
        self.nobility_title()
107
108
109
@dataclass
110
class _Academic_title_default:
111
    academic_title: Optional[str] = field(default=None)
112
113
    def degree_title(self) -> None:
114
        if self.academic_title is not None:
115
            title = self.academic_title
116
            self.academic_title = self.title_repair(title)
117
118
    def title_repair(self, title) -> str:
119
        if ".D" in title:
120
            title = ". ".join(c for c in title.split("."))
121
        if ".A" in title:
122
            title = ". ".join(c for c in title.split("."))
123
        if title.endswith("Dr"):
124
            title = title[:-2] + "Dr."
125
        while "  " in title:
126
            title = title.replace("  ", " ")
127
        title = title.strip()
128
129
        return title
130
131
132
@dataclass
133
class Academic(_Academic_title_default, Name, AttrDisplay):
134
    def __post_init__(self):
135
        Name.__post_init__(self)
136
        self.degree_title()
137
138
139
@dataclass
140
class _Person_default:
141
    gender: str = field(default="unknown")
142
    born: str = field(default="unknown")
143
    date_of_birth: str = field(default="unknown")
144
    age: str = field(default="unknown")
145
    deceased: str = field(default="unknown")
146
    profession: str = field(default="unknown")
147
148
149
@dataclass
150
class Person(
151
    _Peertitle_default,
152
    _Academic_title_default,
153
    _Person_default,
154
    Name,
155
    AttrDisplay,  # noqa
156
):
157
    def __post_init__(self):
158
        Name.__post_init__(self)
159
        Academic.__post_init__(self)
160
        self.get_sex()
161
        self.get_year_of_birth()
162
        self.get_age()
163
164
    def get_sex(self) -> None:
165
        if "-" in self.first_name:
166
            first_name = self.first_name.split("-")[0]
167
        else:
168
            first_name = self.first_name
169
        d = sex.Detector()
170
        gender = d.get_gender(f"{first_name}")
171
        if "female" in gender:
172
            self.gender = "female"
173
        elif "male" in gender:
174
            self.gender = "male"
175
176
    def get_year_of_birth(self) -> None:
177
        if self.date_of_birth != "unknown":
178
            self.born = self.date_of_birth.split(".")[-1]
179
180
    def get_age(self) -> None:
181
        if self.born != "unknown":
182
            born = str(self.born)
183
            if len(born) > 4:
184
                self.deceased = born.strip()[5:]
185
                self.born = born[:4]
186
            else:
187
                today = datetime.date.today()
188
                self.age = str(int(today.year) - int(born.strip()))
189
190
191
@dataclass
192
class _Politician_default:
193
    electoral_ward: str = field(default="ew")
194
    ward_no: Optional[int] = field(default=None)
195
    voter_count: Optional[int] = field(default=None)
196
    minister: Optional[str] = field(default=None)
197
    offices: List[str] = field(default_factory=lambda: [])
198
    parties: List[str] = field(default_factory=lambda: [])
199
200
    def renamed_wards(self):
201
        wards = {
202
            "Kreis Aachen I": "Aachen III",
203
            "Hochsauerlandkreis II – Soest III": "Hochsauerlandkreis II",
204
            "Kreis Aachen II": "Aachen IV"
205
            if self.last_name in ["Wirtz", "Weidenhaupt"]
206
            else "Kreis Aachen I",
207
        }
208
        if self.electoral_ward in wards.keys():
209
            self.electoral_ward = wards[self.electoral_ward]
210
211
    def scrape_wiki_for_ward(self) -> None:
212
        import requests
213
        from bs4 import BeautifulSoup  # type: ignore
214
215
        URL_base = "https://de.wikipedia.org/wiki/Landtagswahlkreis_{}"
216
        URL = URL_base.format(self.electoral_ward)
217
        req = requests.get(URL)
218
        bsObj = BeautifulSoup(req.text, "lxml")
219
        table = bsObj.find(class_="infobox float-right toptextcells")
220
        self.scrape_wiki_table_for_ward(table)
221
222
    def scrape_wiki_table_for_ward(self, table) -> None:
223
        for td in table.find_all("td"):
224
            if "Wahlkreisnummer" in td.text:
225
                ward_no = td.find_next().text.strip()
226
                ward_no = ward_no.split(" ")[0]
227
                self.ward_no = int(ward_no)
228
            elif "Wahlberechtigte" in td.text:
229
                voter_count = td.find_next().text.strip()
230
                voter_count = self.fix_voter_count(voter_count)
231
                self.voter_count = int(voter_count)
232
233
    def fix_voter_count(self, voter_count):
234
        if voter_count[-1] == "]":
235
            voter_count = voter_count[:-3]
236
        if " " in voter_count:
237
            voter_count = "".join(voter_count.split(" "))
238
        else:
239
            voter_count = "".join(voter_count.split("."))
240
        return voter_count
241
242
243
@dataclass
244
class Politician(
245
    _Peertitle_default,
246
    _Academic_title_default,
247
    _Person_default,
248
    _Politician_default,
249
    _Name_default,
250
    Party,
251
    _Name_base,
252
    AttrDisplay,
253
):
254
    def __post_init__(self):
255
        Name.__post_init__(self)
256
        Academic.__post_init__(self)
257
        Noble.__post_init__(self)
258
        Party.__post_init__(self)
259
        Person.get_sex(self)
260
        Person.get_age(self)
261
        self.change_ward()
262
        if self.party_name in GERMAN_PARTIES:
263
            self.parties.append(
264
                Party(self.party_name, self.party_entry, self.party_exit)
265
            )
266
        if self.minister and self.minister not in self.offices:
267
            self.offices.append(self.minister)
268
269
    def add_Party(
270
        self, party_name, party_entry="unknown", party_exit="unknown"
271
    ):  # noqa
272
        if party_name in GERMAN_PARTIES:
273
            if self.party_is_in_parties(party_name, party_entry, party_exit):
274
                pass
275
            else:
276
                self.parties.append(Party(party_name, party_entry, party_exit))
277
                self.party_name = party_name
278
                self.party_entry = party_entry
279
                self.party_exit = party_exit
280
281
    def align_party_entries(
282
        self, party, party_name, party_entry, party_exit
283
    ) -> Party:  # noqa
284
        if party_entry != "unknown" and party.party_entry == "unknown":
285
            party.party_entry = party_entry
286
        if party_exit != "unknown" and party.party_exit == "unknown":
287
            party.party_exit = party_exit
288
        return party
289
290
    def party_is_in_parties(self, party_name, party_entry, party_exit):
291
        parties_tmp = self.parties[:]
292
        for party in parties_tmp:
293
            if party_name == party.party_name:
294
                party_updated = self.align_party_entries(
295
                    party, party_name, party_entry, party_exit
296
                )
297
                self.parties.remove(party)
298
                self.parties.append(party_updated)
299
                self.party_entry = party_updated.party_entry
300
                self.party_exit = party_updated.party_exit
301
                return True
302
        return False
303
304
    def change_ward(self, ward=None):
305
        if ward:
306
            self.electoral_ward = ward
307
        if self.electoral_ward not in ["ew", "Landesliste"]:
308
            self.renamed_wards()
309
            self.scrape_wiki_for_ward()
310
        else:
311
            self.electoral_ward = "ew"
312
313
314
if __name__ == "__main__":
315
316
    name = Name("Hans Hermann", "Werner")
317
    print(name)
318
319
    noble = Noble("Dagmara", "Bodelschwingh", peer_title="Gräfin von")
320
    print(noble)
321
322
    academic = Academic("Horst Heiner", "Wiekeiner", academic_title="Dr.")  # noqa
323
    print(academic)
324
325
    person_1 = Person("Sven", "Rübennase", academic_title="MBA", born="1990")  # noqa
326
    print(person_1)
327
328
    politician = Politician(
329
        "Bärbel",
330
        "Gutherz",
331
        "SPD",
332
        academic_title="Dr.",
333
        born="1980",
334
        electoral_ward="Köln I",
335
    )
336
    print(politician)
337