Quellcode durchsuchen

Black, server check

Maarten van den Berg vor 6 Jahren
Ursprung
Commit
042ebf41ba
6 geänderte Dateien mit 130 neuen und 49 gelöschten Zeilen
  1. 41 20
      piket_client/gui.py
  2. 49 6
      piket_client/logger.py
  3. 25 11
      piket_client/model.py
  4. 6 5
      piket_server/__init__.py
  5. 6 7
      piket_server/seed.py
  6. 3 0
      setup.py

+ 41 - 20
piket_client/gui.py

@@ -7,6 +7,7 @@ import os
7 7
 import sys
8 8
 
9 9
 import qdarkstyle
10
+
10 11
 # pylint: disable=E0611
11 12
 from PySide2.QtWidgets import (
12 13
     QAction,
@@ -32,7 +33,7 @@ except ImportError:
32 33
     dbus = None
33 34
 
34 35
 from piket_client.sound import PLOP_WAVE, UNDO_WAVE
35
-from piket_client.model import Person, ConsumptionType, Consumption
36
+from piket_client.model import Person, ConsumptionType, Consumption, ServerStatus
36 37
 import piket_client.logger
37 38
 
38 39
 LOG = logging.getLogger(__name__)
@@ -60,7 +61,6 @@ class NameButton(QPushButton):
60 61
         self.setContextMenuPolicy(Qt.CustomContextMenu)
61 62
         self.customContextMenuRequested.connect(self.confirm_hide)
62 63
 
63
-
64 64
     @Slot(str)
65 65
     def new_active_id(self, new_id: str) -> None:
66 66
         """ Change the active ConsumptionType id, update the label. """
@@ -97,14 +97,14 @@ class NameButton(QPushButton):
97 97
     def confirm_hide(self) -> None:
98 98
         ok = QMessageBox.warning(
99 99
             self.window(),
100
-            'Persoon verbergen?',
101
-            f'Wil je {self.person.name} verbergen?',
100
+            "Persoon verbergen?",
101
+            f"Wil je {self.person.name} verbergen?",
102 102
             QMessageBox.Yes,
103 103
             QMessageBox.Cancel,
104 104
         )
105 105
 
106 106
         if ok == QMessageBox.Yes:
107
-            LOG.warning('Hiding person %s', self.person.name)
107
+            LOG.warning("Hiding person %s", self.person.name)
108 108
             self.person.set_active(False)
109 109
             self.parent().init_ui()
110 110
 
@@ -131,7 +131,6 @@ class NameButtons(QWidget):
131 131
         self.active_consumption_type_id = new_id
132 132
         self.new_id_set.emit(new_id)
133 133
 
134
-
135 134
     def init_ui(self) -> None:
136 135
         """ Initialize UI: build GridLayout, retrieve People and build a button
137 136
         for each. """
@@ -183,9 +182,13 @@ class PiketMainWindow(QMainWindow):
183 182
                 self.osk = session_bus.get_object(
184 183
                     "org.onboard.Onboard", "/org/onboard/Onboard/Keyboard"
185 184
                 )
186
-            except dbus.exceptions.DBusException:
185
+            except dbus.exceptions.DBusException as exception:
187 186
                 # Onboard not present or dbus broken
188 187
                 self.osk = None
188
+                LOG.error("Could not connect to Onboard:")
189
+                LOG.exception(exception)
190
+        else:
191
+            LOG.warning("Onboard disabled due to missing dbus.")
189 192
 
190 193
         # Go full screen
191 194
         self.setWindowState(Qt.WindowActive | Qt.WindowFullScreen)
@@ -215,8 +218,10 @@ class PiketMainWindow(QMainWindow):
215 218
 
216 219
         # Right
217 220
         self.toolbar.addAction(
218
-            self.load_icon("add_consumption_type.svg"), "Nieuw",
219
-            self.add_consumption_type)
221
+            self.load_icon("add_consumption_type.svg"),
222
+            "Nieuw",
223
+            self.add_consumption_type,
224
+        )
220 225
         self.toolbar.setContextMenuPolicy(Qt.PreventContextMenu)
221 226
         self.toolbar.setFloatable(False)
222 227
         self.toolbar.setMovable(False)
@@ -257,8 +262,9 @@ class PiketMainWindow(QMainWindow):
257 262
                 sys.exit()
258 263
 
259 264
         for ct in cts:
260
-            action = QAction(self.load_icon(ct.icon or "beer_bottle.svg"),
261
-                    ct.name, self.ct_ag)
265
+            action = QAction(
266
+                self.load_icon(ct.icon or "beer_bottle.svg"), ct.name, self.ct_ag
267
+            )
262 268
             action.setCheckable(True)
263 269
             action.setData(str(ct.consumption_type_id))
264 270
 
@@ -319,10 +325,7 @@ class PiketMainWindow(QMainWindow):
319 325
     def add_consumption_type(self) -> None:
320 326
         self.show_keyboard()
321 327
         name, ok = QInputDialog.getItem(
322
-            self,
323
-            "Lijst toevoegen",
324
-            "Wat wil je strepen?",
325
-            ["Wijn", "Radler"]
328
+            self, "Lijst toevoegen", "Wat wil je strepen?", ["Wijn", "Radler"]
326 329
         )
327 330
         self.hide_keyboard()
328 331
 
@@ -331,9 +334,7 @@ class PiketMainWindow(QMainWindow):
331 334
             ct = ct.create()
332 335
 
333 336
             action = QAction(
334
-                self.load_icon(ct.icon or "beer_bottle.svg"),
335
-                ct.name,
336
-                self.ct_ag
337
+                self.load_icon(ct.icon or "beer_bottle.svg"), ct.name, self.ct_ag
337 338
             )
338 339
             action.setCheckable(True)
339 340
             action.setData(str(ct.consumption_type_id))
@@ -379,7 +380,6 @@ class PiketMainWindow(QMainWindow):
379 380
         self.undo_queue.append(consumption)
380 381
         self.undo_action.setDisabled(False)
381 382
 
382
-
383 383
     @staticmethod
384 384
     def create_spacer() -> QWidget:
385 385
         """ Return an empty QWidget that automatically expands. """
@@ -394,7 +394,7 @@ class PiketMainWindow(QMainWindow):
394 394
         directory. """
395 395
 
396 396
         if self.dark_theme:
397
-            filename = 'white_' + filename
397
+            filename = "white_" + filename
398 398
 
399 399
         icon = QIcon(os.path.join(self.icons_dir, filename))
400 400
         return icon
@@ -402,16 +402,37 @@ class PiketMainWindow(QMainWindow):
402 402
 
403 403
 def main() -> None:
404 404
     """ Main entry point of GUI client. """
405
+    LOG.info("Loading piket_client")
405 406
     app = QApplication(sys.argv)
407
+
408
+    # Set dark theme
406 409
     app.setStyleSheet(qdarkstyle.load_stylesheet_pyside2())
410
+
411
+    # Enlarge font size
407 412
     font = app.font()
408 413
     size = font.pointSize()
409 414
     font.setPointSize(size * 1.5)
410 415
     app.setFont(font)
411 416
 
417
+    # Test connectivity
418
+    server_running, info = ServerStatus.is_server_running()
419
+
420
+    if not server_running:
421
+        LOG.critical("Could not connect to server", extra={"info": info})
422
+        QMessageBox.critical(
423
+            None,
424
+            "Help er is iets kapot",
425
+            "Kan niet starten omdat de server niet reageert, stuur een foto van "
426
+            "dit naar Maarten: " + repr(info),
427
+        )
428
+        return 1
429
+
430
+    # Load main window
412 431
     main_window = PiketMainWindow()
413 432
     main_window.show()
414 433
 
434
+    # Let's go
435
+    LOG.info("Starting QT event loop.")
415 436
     app.exec_()
416 437
 
417 438
 

+ 49 - 6
piket_client/logger.py

@@ -1,9 +1,52 @@
1 1
 import os.path
2 2
 import logging
3
-from logging import getLogger
3
+import logging.config
4 4
 
5
-logging.basicConfig(
6
-    format="%(asctime)s - %(name)s - %(levelname)s: %(message)s",
7
-    level=logging.INFO,
8
-    filename=os.path.expanduser('~/.piket_client.log')
9
-)
5
+try:
6
+    from raven import Client
7
+    from raven.handlers.logging import SentryHandler
8
+    from raven.conf import setup_logging
9
+
10
+    HAVE_SENTRY = True
11
+except ImportError:
12
+    HAVE_SENTRY = False
13
+
14
+LOG_CONFIG = {
15
+    "version": 1,
16
+    "disable_existing_loggers": True,
17
+    "formatters": {
18
+        "console": {
19
+            "format": "[%(asctime)s][%(levelname)s] %(name)s "
20
+            "%(filename)s:%(funcName)s:%(lineno)d | %(message)s",
21
+            "datefmt": "%H:%M:%S",
22
+        }
23
+    },
24
+    "handlers": {
25
+        "console": {
26
+            "level": "DEBUG",
27
+            "class": "logging.StreamHandler",
28
+            "formatter": "console",
29
+        },
30
+        "file": {
31
+            "level": "INFO",
32
+            "class": "logging.FileHandler",
33
+            "formatter": "console",
34
+            "filename": os.path.expanduser("~/.piket_client.log"),
35
+        },
36
+    },
37
+    "loggers": {
38
+        "": {"handlers": ["console", "file"], "level": "INFO", "propagate": False},
39
+        "piket_client": {"level": "INFO", "propagate": True},
40
+    },
41
+}
42
+
43
+if HAVE_SENTRY and os.environ.get("SENTRY_DSN"):
44
+    LOG_CONFIG["handlers"]["sentry"] = {
45
+        "level": "ERROR",
46
+        "class": "raven.handlers.logging.SentryHandler",
47
+        "dsn": os.environ.get("SENTRY_DSN"),
48
+    }
49
+
50
+    LOG_CONFIG["loggers"][""]["handlers"].append("sentry")
51
+
52
+logging.config.dictConfig(LOG_CONFIG)

+ 25 - 11
piket_client/model.py

@@ -2,18 +2,34 @@
2 2
 Provides access to the models stored in the database, via the server.
3 3
 """
4 4
 import datetime
5
+import logging
5 6
 from typing import NamedTuple
6 7
 from urllib.parse import urljoin
7 8
 
8 9
 import requests
9 10
 
10
-from . import logger
11 11
 
12
-LOG = logger.getLogger("model")
12
+LOG = logging.getLogger(__name__)
13 13
 
14 14
 SERVER_URL = "http://127.0.0.1:5000"
15 15
 
16 16
 
17
+class ServerStatus:
18
+    """ Provides helper classes to check whether the server is up. """
19
+
20
+    @classmethod
21
+    def is_server_running(cls) -> bool:
22
+        try:
23
+            req = requests.get(urljoin(SERVER_URL, "ping"))
24
+
25
+            if req.status_code == 200:
26
+                return True, req.content
27
+            return False, req.content
28
+
29
+        except requests.ConnectionError as ex:
30
+            return False, ex
31
+
32
+
17 33
 class Person(NamedTuple):
18 34
     """ Represents a Person, as retrieved from the database. """
19 35
 
@@ -73,8 +89,10 @@ class Person(NamedTuple):
73 89
         return Person.from_dict(data["person"])
74 90
 
75 91
     def set_active(self, new_state=True) -> "Person":
76
-        req = requests.patch(urljoin(SERVER_URL, f"people/{self.person_id}"),
77
-                json={'person': {'active':new_state}})
92
+        req = requests.patch(
93
+            urljoin(SERVER_URL, f"people/{self.person_id}"),
94
+            json={"person": {"active": new_state}},
95
+        )
78 96
 
79 97
         try:
80 98
             data = req.json()
@@ -92,8 +110,6 @@ class Person(NamedTuple):
92 110
 
93 111
         return Person.from_dict(data["person"])
94 112
 
95
-
96
-
97 113
     @classmethod
98 114
     def get(cls, person_id: int) -> "Person":
99 115
         """ Retrieve a Person by id. """
@@ -122,8 +138,7 @@ class Person(NamedTuple):
122 138
     def get_all(cls, active=None) -> ["Person"]:
123 139
         """ Get all active People. """
124 140
         active = int(active)
125
-        req = requests.get(urljoin(SERVER_URL, "/people"),
126
-                params={'active':active})
141
+        req = requests.get(urljoin(SERVER_URL, "/people"), params={"active": active})
127 142
 
128 143
         try:
129 144
             data = req.json()
@@ -260,15 +275,14 @@ class Consumption(NamedTuple):
260 275
     @classmethod
261 276
     def from_dict(cls, data: dict) -> "Consumption":
262 277
         """ Reconstruct a Consumption from a dict. """
263
-        datetime_format = '%Y-%m-%dT%H:%M:%S.%f'
278
+        datetime_format = "%Y-%m-%dT%H:%M:%S.%f"
264 279
         # 2018-08-31T17:30:47.871521
265 280
         return cls(
266 281
             consumption_id=data["consumption_id"],
267 282
             person_id=data["person_id"],
268 283
             consumption_type_id=data["consumption_type_id"],
269 284
             settlement_id=data["settlement_id"],
270
-            created_at=datetime.datetime.strptime(data["created_at"],
271
-                datetime_format),
285
+            created_at=datetime.datetime.strptime(data["created_at"], datetime_format),
272 286
             reversed=data["reversed"],
273 287
         )
274 288
 

+ 6 - 5
piket_server/__init__.py

@@ -138,8 +138,8 @@ def get_people():
138 138
     """ Return a list of currently known people. """
139 139
     people = Person.query.order_by(Person.name).all()
140 140
     q = Person.query.order_by(Person.name)
141
-    if request.args.get('active'):
142
-        active_status = request.args.get('active', type=int)
141
+    if request.args.get("active"):
142
+        active_status = request.args.get("active", type=int)
143 143
         q = q.filter_by(active=active_status)
144 144
     people = q.all()
145 145
     result = [person.as_dict for person in people]
@@ -195,14 +195,15 @@ def add_consumption(person_id: int):
195 195
 
196 196
     return jsonify(person=person.as_dict, consumption=consumption.as_dict), 201
197 197
 
198
+
198 199
 @app.route("/people/<int:person_id>", methods=["PATCH"])
199 200
 def update_person(person_id: int):
200 201
     person = Person.query.get_or_404(person_id)
201 202
 
202
-    data = request.json['person']
203
+    data = request.json["person"]
203 204
 
204
-    if 'active' in data:
205
-        person.active = data['active']
205
+    if "active" in data:
206
+        person.active = data["active"]
206 207
 
207 208
         db.session.add(person)
208 209
         db.session.commit()

+ 6 - 7
piket_server/seed.py

@@ -21,10 +21,11 @@ def main():
21 21
     parser_clear.add_argument("--removemydata", action="store_true")
22 22
 
23 23
     # Parser load_seeds
24
-    parser_load_persons = subparsers.add_parser("load_persons",
25
-            help="Load Person data.")
24
+    parser_load_persons = subparsers.add_parser(
25
+        "load_persons", help="Load Person data."
26
+    )
26 27
     parser_load_persons.set_defaults(func=cmd_load_persons)
27
-    parser_load_persons.add_argument('datafile')
28
+    parser_load_persons.add_argument("datafile")
28 29
 
29 30
     args = parser.parse_args()
30 31
     args.func(args)
@@ -62,6 +63,7 @@ def cmd_clear(args) -> None:
62 63
 
63 64
     print("Aborting.")
64 65
 
66
+
65 67
 def cmd_load_persons(args) -> None:
66 68
     """ Entrypoint for 'load_persons" subcommand. """
67 69
 
@@ -71,10 +73,7 @@ def cmd_load_persons(args) -> None:
71 73
         reader = csv.DictReader(csvfile)
72 74
 
73 75
         for row in reader:
74
-            p = Person(
75
-                name=row['name'],
76
-                active=bool(int(row['active']))
77
-            )
76
+            p = Person(name=row["name"], active=bool(int(row["active"])))
78 77
             db.session.add(p)
79 78
 
80 79
     db.session.commit()

+ 3 - 0
setup.py

@@ -54,6 +54,9 @@ setup(
54 54
             'osk': [
55 55
                 'dbus-python',
56 56
             ],
57
+            'sentry': [
58
+                'raven',
59
+            ],
57 60
         },
58 61
 
59 62
         include_package_data=True,