Politician.align_party_entries()   A
last analyzed

Complexity

Conditions 5

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 8
rs 9.3333
c 0
b 0
f 0
cc 5
nop 5
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
"""A set of dataclasses concerning roles of persons and their particulars."""
4
import os
5
import sys
6
from dataclasses import dataclass, field
7
from typing import List, Optional
8
9
PACKAGE_PARENT = ".."
10
SCRIPT_DIR = os.path.dirname(
11
    os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__)))
12
)  # isort:skip # noqa # pylint: disable=wrong-import-position
13
sys.path.append(
14
    os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT))
15
)  # isort: skip # noqa # pylint: disable=wrong-import-position
16
17
from personroles.person import Person  # type: ignore  # noqa
18
from personroles.resources import helpers  # type: ignore  # noqa
19
from personroles.resources.constants import (  # type: ignore # noqa
0 ignored issues
show
Unused Code introduced by
Unused PEERTITLES imported from personroles.resources.constants
Loading history...
20
    GERMAN_PARTIES,
21
    PEERTITLES,
22
)
23
from personroles.resources.helpers import AttrDisplay  # type: ignore # noqa
24
from personroles.resources.helpers import Party  # type: ignore # noqa
25
26
27
@dataclass
0 ignored issues
show
Coding Style Naming introduced by
Class name "_Politician_default" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
28
class _Politician_default:
29
    """Data about the politician's party, ward and office(s)."""
30
    electoral_ward: str = field(default="ew")
31
    ward_no: Optional[int] = field(default=None)
32
    voter_count: Optional[int] = field(default=None)
33
    minister: Optional[str] = field(default=None)
34
    offices: List[str] = field(default_factory=lambda: [])
35
    parties: List[str] = field(default_factory=lambda: [])
36
37
    def renamed_wards(self):
38
        """Some electoral wards have been renamed in the Wikipedia."""
39
        wards = {
40
            "Kreis Aachen I": "Aachen III",
41
            "Hochsauerlandkreis II – Soest III": "Hochsauerlandkreis II",
42
            "Kreis Aachen II": "Aachen IV"
43
            if self.last_name in ["Wirtz", "Weidenhaupt"]
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 19 spaces).
Loading history...
Bug introduced by
The Instance of _Politician_default does not seem to have a member named last_name.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
44
            else "Kreis Aachen I",
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 19 spaces).
Loading history...
45
        }
46
        if self.electoral_ward in wards.keys():
47
            self.electoral_ward = wards[self.electoral_ward]
48
49
    def scrape_wiki_for_ward(self) -> None:
50
        """Find tables in Wikipedia containing informations about electoral wards."""  # noqa
51
        import requests
0 ignored issues
show
introduced by
Import outside toplevel (requests)
Loading history...
introduced by
Unable to import 'requests'
Loading history...
52
        from bs4 import BeautifulSoup  # type: ignore
0 ignored issues
show
introduced by
Unable to import 'bs4'
Loading history...
introduced by
Import outside toplevel (bs4.BeautifulSoup)
Loading history...
53
54
        URL_base = "https://de.wikipedia.org/wiki/Landtagswahlkreis_{}"
0 ignored issues
show
Coding Style Naming introduced by
Variable name "URL_base" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
55
        URL = URL_base.format(self.electoral_ward)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "URL" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
56
        req = requests.get(URL)
57
        bsObj = BeautifulSoup(req.text, "lxml")
0 ignored issues
show
Coding Style Naming introduced by
Variable name "bsObj" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
58
        table = bsObj.find(class_="infobox float-right toptextcells")
59
        self.scrape_wiki_table_for_ward(table)
60
61
    def scrape_wiki_table_for_ward(self, table) -> None:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
62
        for td in table.find_all("td"):
0 ignored issues
show
Coding Style Naming introduced by
Variable name "td" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
63
            if "Wahlkreisnummer" in td.text:
64
                ward_no = td.find_next().text.strip()
65
                ward_no = ward_no.split(" ")[0]
66
                self.ward_no = int(ward_no)
67
            elif "Wahlberechtigte" in td.text:
68
                voter_count = td.find_next().text.strip()
69
                voter_count = self.fix_voter_count(voter_count)
70
                self.voter_count = int(voter_count)
71
72
    def fix_voter_count(self, voter_count):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
73
        if voter_count[-1] == "]":
74
            voter_count = voter_count[:-3]
75
        if " " in voter_count:
76
            voter_count = "".join(voter_count.split(" "))
77
        else:
78
            voter_count = "".join(voter_count.split("."))
79
        return voter_count
80
81
82
@dataclass
0 ignored issues
show
best-practice introduced by
Too many ancestors (12/7)
Loading history...
83
class Politician(
84
    _Politician_default,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
85
    helpers._Party_default,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
Coding Style Best Practice introduced by
It seems like _Party_default was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
86
    Person,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
87
    helpers._Party_base,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
Coding Style Best Practice introduced by
It seems like _Party_base was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
88
    AttrDisplay,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
89
):
90
91
    """
92
    Module politician_role.py is collecting electoral ward, ward no., voter
93
    count of that ward, minister (like "JM": Justizminister), offices (in case
94
    more than one ministry position is filled (i.e. ["JM", "FM"]), and parties.
95
    """
96
97
    def __post_init__(self):
98
        Party.__post_init__(self)
99
        Person.__post_init__(self)
100
        Person.get_sex(self)
101
        Person.get_age(self)
102
        self.change_ward()
103
        if self.party_name in GERMAN_PARTIES:
104
            self.parties.append(
105
                Party(self.party_name, self.party_entry, self.party_exit)
106
            )
107
        if self.minister and self.minister not in self.offices:
108
            self.offices.append(self.minister)
109
110
    def add_Party(
0 ignored issues
show
Coding Style Naming introduced by
Method name "add_Party" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
introduced by
Missing function or method docstring
Loading history...
111
        self, party_name, party_entry="unknown", party_exit="unknown"
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
112
    ):  # noqa
113
        if party_name in GERMAN_PARTIES:
114
            if self.party_is_in_parties(party_name, party_entry, party_exit):
115
                pass
116
            else:
117
                self.parties.append(Party(party_name, party_entry, party_exit))
118
                self.party_name = party_name
0 ignored issues
show
Coding Style introduced by
The attribute party_name was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
119
                self.party_entry = party_entry
120
                self.party_exit = party_exit
121
122
    def align_party_entries(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
123
        self, party, party_name, party_entry, party_exit
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
Unused Code introduced by
The argument party_name seems to be unused.
Loading history...
124
    ) -> Party:  # noqa
125
        if party_entry != "unknown" and party.party_entry == "unknown":
126
            party.party_entry = party_entry
127
        if party_exit != "unknown" and party.party_exit == "unknown":
128
            party.party_exit = party_exit
129
        return party
130
131
    def party_is_in_parties(self, party_name, party_entry, party_exit):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
132
        parties_tmp = self.parties[:]
133
        for party in parties_tmp:
134
            if party_name == party.party_name:
135
                party_updated = self.align_party_entries(
136
                    party, party_name, party_entry, party_exit
137
                )
138
                self.parties.remove(party)
139
                self.parties.append(party_updated)
140
                self.party_entry = party_updated.party_entry
141
                self.party_exit = party_updated.party_exit
142
                return True
143
        return False
144
145
    def change_ward(self, ward=None):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
146
        if ward:
147
            self.electoral_ward = ward
148
        if self.electoral_ward not in ["ew", "Landesliste"]:
149
            self.renamed_wards()
150
            self.scrape_wiki_for_ward()
151
        else:
152
            self.electoral_ward = "ew"
153
154
155
if __name__ == "__main__":
156
157
    politician = Politician(
158
        "SPD",
159
        "Bärbel",
160
        "Gutherz",
161
        academic_title="Dr.",
162
        date_of_birth="1980",
163
        electoral_ward="Köln I",
164
    )
165
    print(politician)
166