Completed
Push — master ( d8600a...1d41d0 )
by Oliver
02:04
created

person._Peertitle_default.nobility_title()   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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