Passed
Pull Request — master (#50)
by Cyb3r
01:40
created

bot.utils.datahandler._format_step()   B

Complexity

Conditions 7

Size

Total Lines 46
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 20
nop 1
dl 0
loc 46
rs 8
c 0
b 0
f 0
1
"""Handles all postgresql data and tables"""
2
import os
3
import psycopg2
4
import psycopg2.errors
5
from psycopg2.extensions import AsIs
6
from .tables import tables
7
from .logger import make_logger
8
9
# Imports the main logger
10
log = make_logger("database", os.getenv("LOG_LEVEL", "INFO"))
11
12
# Creates the connection to the database
13
connection = psycopg2.connect(os.getenv("DATABASE_URL"), sslmode="require")
14
cursor = connection.cursor()
15
16
DuplicateError = psycopg2.errors.lookup("23505")
17
18
19
def table_create() -> None:
20
    """Table_create
21
22
    Creates tables if they do not exist at startup. All tables are pulled from tables.py
23
24
    """
25
    for table in tables:
26
        cursor.execute(table)
27
28
29
def _format_step(table: str) -> str:
30
    """Format_step
31
    ---
32
33
    Returns the format string to be used in insert. This was split from insert to make it less
34
        complex and easier to read.
35
36
    Arguments:
37
    ---
38
        table {str} -- The name of the table for the insert
39
40
    Returns:
41
    ---
42
        str -- Returns a string that will be used for cursor execution
43
    """
44
    if table == "schools":
45
        query_str = (
46
            "INSERT INTO schools"
47
            "(school, region, color, id, added_by, added_by_id) "
48
            "VALUES (%s, %s, %s, %s, %s, %s);"
49
        )
50
    elif table == "errors":
51
        query_str = (
52
            "INSERT INTO errors"
53
            "(id, command, message, error, time, ack) "
54
            "VALUES (%s, %s, %s, %s, %s, %s);"
55
        )
56
    elif table == "reports":
57
        query_str = (
58
            "INSERT INTO reports"
59
            "(id, name, name_id, message, time) "
60
            "VALUES (%s, %s, %s, %s, %s);"
61
        )
62
    elif table == "admin_channels":
63
        query_str = (
64
            "INSERT INTO admin_channels (name, id, log) "
65
            "VALUES (%s, %s, %s) ON CONFLICT DO NOTHING;"
66
        )
67
    elif table == "bot_admins":
68
        query_str = "INSERT INTO bot_admins (name, id) VALUES (%s, %s) ON CONFLICT DO NOTHING;"
69
    elif table == "regions":
70
        query_str = "INSERT INTO regions (name, id) VALUES (%s, %s)"
71
    else:
72
        log.error("Table {} not found.".format(table))
73
        return "error"
74
    return query_str
75
76
77
def _result_parser(column: str, fetched: list) -> list:
78
    """Result_parser
79
    ---
80
81
    Arguments:
82
    ---
83
        column {str} -- The name of the column(s) only used to determine how to parse the results.
84
        fetched {list} -- The results pulled from the table.
85
86
    Returns:
87
    ---
88
        list -- A normal list or a list of tuples.
89
    """
90
    # fetched is a list so it does not need breaking up
91
    if column == "*" or column.find(",") != -1:
92
        result = fetched
93
    # Breaks up the tuples to a standard list.
94
    else:
95
        result = [x[0] for x in fetched]
96
    return result
97
98
99
async def insert(table: str, data: list) -> [None, str]:
100
    """Insert
101
    ---
102
103
    Asynchronous Function
104
105
    Inserts a new row to an existing table. Get the string to execute with from :ref:`_format_step`.
106
107
    Arguments:
108
    ---
109
        table {str} -- c to perform the insert on
110
        data {list} -- The data that gets placed into the format_str
111
112
    Returns:
113
    ---
114
        str -- In the event of an error inserting into the table the string 'error' will be
115
            returned. If there is no error then 'None' will be returned.
116
    PostgreSQL Equivalent:
117
    ---
118
    INSERT into :ref:`table` VALUE (:ref:`*data`);
119
    """
120
    format_str = _format_step(table)
121
    if format_str == "error":
122
        return "error"
123
    log.debug("String: {} Data {}".format(format_str, " ".join(map(str, data))))
124
    try:
125
        # Tables with 6 values
126
        if table in ["schools", "errors"]:
127
            cursor.execute(format_str, (data[0], data[1], data[2], data[3], data[4], data[5]))
128
        # Tables with 5 values
129
        elif table in ["reports"]:
130
            cursor.execute(format_str, (data[0], data[1], data[2], data[3], data[4]))
131
        # Tables with 3 values
132
        elif table == "admin_channels":
133
            cursor.execute(format_str, (data[0], data[1], data[2]))
134
        # Tables with 2 values
135
        elif table in ["bot_admins", "regions"]:
136
            cursor.execute(format_str, (data[0], data[1]))
137
        connection.commit()
138
        return None
139
    except psycopg2.Error as pge:
140
        log.error(pge)
141
        cursor.rollback()
142
        if isinstance(pge, DuplicateError):
143
            return "duplicate"
144
        return "error"
145
146
147
async def fetch(table: str, column: str) -> list:
148
    """Fetch
149
    ---
150
151
    Asynchronous Function
152
153
    Retrieves values from the :ref:`column` from the :ref:`table`.
154
155
    Arguments:
156
    ---
157
        table {str} -- Name of the table that data is being fetched from.
158
        column {str} -- The column(s) that is being fetched. Multiple columns need to comma
159
            separated, if all columns are wanted then use '*'.
160
161
    Returns:
162
    ---
163
        list -- A list of tuples for '*' or multiple columns. For one column it is a list.
164
165
    Postgresql Equivalent:
166
    ---
167
    SELECT :ref:`column` from :ref:`table`;
168
    """
169
    try:
170
        format_str = "SELECT %s FROM %s;"
171
        cursor.execute(format_str, (AsIs(column), AsIs(table)))
172
        fetched = cursor.fetchall()
173
        return _result_parser(column, fetched)
174
    except psycopg2.Error as pge:
175
        log.error(pge)
176
177
178
async def select(
179
    table: str,
180
    column: str,
181
    where_column: str,
182
    where_value: [str, bool, int],
183
    symbol: [str, bool] = "=",
184
) -> list:
185
    """Select
186
    ---
187
188
    Asynchronous Function
189
190
    Selects one row from the table based on selector.
191
192
193
    Arguments:
194
    ---
195
        table {str} -- Name of the table that data is being fetched from.
196
        column {str} -- The column(s) that is being fetched. Multiple columns need to comma
197
            separated, if all columns are wanted then use '*'.
198
        where_column {str} -- The column that is going have the value of :ref:`where_value`.
199
        where_value {str} -- The value that you are matching.
200
        symbol {str} -- [description] (default: {"="})
201
202
    Returns:
203
    ---
204
        list -- List of values that are the results.
205
206
    Postgresql Equivalent:
207
    ---
208
    SELECT :ref:`column` FROM :ref:`table` WHERE :ref:`where_column` :ref:`symbol` :ref:`where_value`;  # noqa: E501 pylint: disable=line-too-long
209
    """
210
    try:
211
        format_str = "SELECT %s FROM %s WHERE %s %s %s;"
212
        cursor.execute(
213
            format_str,
214
            (AsIs(column), AsIs(table), AsIs(where_column), AsIs(symbol), where_value),
215
        )
216
        fetched = cursor.fetchall()
217
        return _result_parser(column, fetched)
218
    except psycopg2.Error as pge:
219
        log.error(pge)
220
        cursor.execute("ROLLBACK")
221
222
223
async def update(
224
    table: str, column: str, where_value: str, new_value: [str, bool], where_column: str = ""
225
) -> None:
226
    """Update
227
    ---
228
229
    Asynchronous Function
230
231
    Updates a value in the table
232
233
    Arguments:
234
        table {str} -- Name of the table that the data is being updated on.
235
        column {str} -- The column that is being updated. Multiple columns are not supported.
236
        where_value {str, bool} -- The value that is going to be updated.
237
        new_value {str} -- The new value for :ref:`where_value`
238
239
    Postgresql Equivalent:
240
    ---
241
    UPDATE :ref:`table` SET :ref:`column` = :ref:`new_value`
242
    WHERE :ref:`check_column` = :ref:`where_value`;
243
    """
244
    if not where_column:
245
        where_column = column
246
    try:
247
        format_str = "UPDATE %s SET %s = %s where %s = %s"
248
        cursor.execute(
249
            format_str,
250
            (AsIs(table), AsIs(column), new_value, AsIs(where_column), where_value),
251
        )
252
        connection.commit()
253
    except psycopg2.Error as pge:
254
        log.error(pge)
255
        cursor.execute("ROLLBACK")
256
257
258
async def delete(table: str, column: str, value: str) -> None:
259
    """Delete
260
    ---
261
    Asynchronous Function
262
263
    Removes an entry from the table where :ref:`value` is equal.
264
265
    Arguments:
266
    ---
267
        table {str} -- Name of the table that the data is being deleted from.
268
269
        column {str} -- The column to which the :ref:`value` is going to match.
270
271
        value {str} -- The value in the row which is going to match to a value in :ref:`column`
272
273
    Postgresql Equivalent:
274
    ---
275
    DELETE FROM :ref:`table` WHERE :ref:`column` = :ref:`value`;
276
    """
277
    try:
278
        log.info("Deleting {} where {} from {}".format(column, value, table))
279
        format_str = "DELETE FROM %s WHERE %s = %s"
280
        cursor.execute(format_str, (AsIs(table), AsIs(column), value))
281
        connection.commit()
282
    except psycopg2.Error as pge:
283
        log.error(pge)
284
        cursor.execute("ROLLBACK")
285