Browse Source

Black, server check

Maarten van den Berg 6 years ago
parent
commit
042ebf41ba
6 changed files with 130 additions and 49 deletions
  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
 import sys
7
 import sys
8
 
8
 
9
 import qdarkstyle
9
 import qdarkstyle
10
+
10
 # pylint: disable=E0611
11
 # pylint: disable=E0611
11
 from PySide2.QtWidgets import (
12
 from PySide2.QtWidgets import (
12
     QAction,
13
     QAction,
32
     dbus = None
33
     dbus = None
33
 
34
 
34
 from piket_client.sound import PLOP_WAVE, UNDO_WAVE
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
 import piket_client.logger
37
 import piket_client.logger
37
 
38
 
38
 LOG = logging.getLogger(__name__)
39
 LOG = logging.getLogger(__name__)
60
         self.setContextMenuPolicy(Qt.CustomContextMenu)
61
         self.setContextMenuPolicy(Qt.CustomContextMenu)
61
         self.customContextMenuRequested.connect(self.confirm_hide)
62
         self.customContextMenuRequested.connect(self.confirm_hide)
62
 
63
 
63
-
64
     @Slot(str)
64
     @Slot(str)
65
     def new_active_id(self, new_id: str) -> None:
65
     def new_active_id(self, new_id: str) -> None:
66
         """ Change the active ConsumptionType id, update the label. """
66
         """ Change the active ConsumptionType id, update the label. """
97
     def confirm_hide(self) -> None:
97
     def confirm_hide(self) -> None:
98
         ok = QMessageBox.warning(
98
         ok = QMessageBox.warning(
99
             self.window(),
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
             QMessageBox.Yes,
102
             QMessageBox.Yes,
103
             QMessageBox.Cancel,
103
             QMessageBox.Cancel,
104
         )
104
         )
105
 
105
 
106
         if ok == QMessageBox.Yes:
106
         if ok == QMessageBox.Yes:
107
-            LOG.warning('Hiding person %s', self.person.name)
107
+            LOG.warning("Hiding person %s", self.person.name)
108
             self.person.set_active(False)
108
             self.person.set_active(False)
109
             self.parent().init_ui()
109
             self.parent().init_ui()
110
 
110
 
131
         self.active_consumption_type_id = new_id
131
         self.active_consumption_type_id = new_id
132
         self.new_id_set.emit(new_id)
132
         self.new_id_set.emit(new_id)
133
 
133
 
134
-
135
     def init_ui(self) -> None:
134
     def init_ui(self) -> None:
136
         """ Initialize UI: build GridLayout, retrieve People and build a button
135
         """ Initialize UI: build GridLayout, retrieve People and build a button
137
         for each. """
136
         for each. """
183
                 self.osk = session_bus.get_object(
182
                 self.osk = session_bus.get_object(
184
                     "org.onboard.Onboard", "/org/onboard/Onboard/Keyboard"
183
                     "org.onboard.Onboard", "/org/onboard/Onboard/Keyboard"
185
                 )
184
                 )
186
-            except dbus.exceptions.DBusException:
185
+            except dbus.exceptions.DBusException as exception:
187
                 # Onboard not present or dbus broken
186
                 # Onboard not present or dbus broken
188
                 self.osk = None
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
         # Go full screen
193
         # Go full screen
191
         self.setWindowState(Qt.WindowActive | Qt.WindowFullScreen)
194
         self.setWindowState(Qt.WindowActive | Qt.WindowFullScreen)
215
 
218
 
216
         # Right
219
         # Right
217
         self.toolbar.addAction(
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
         self.toolbar.setContextMenuPolicy(Qt.PreventContextMenu)
225
         self.toolbar.setContextMenuPolicy(Qt.PreventContextMenu)
221
         self.toolbar.setFloatable(False)
226
         self.toolbar.setFloatable(False)
222
         self.toolbar.setMovable(False)
227
         self.toolbar.setMovable(False)
257
                 sys.exit()
262
                 sys.exit()
258
 
263
 
259
         for ct in cts:
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
             action.setCheckable(True)
268
             action.setCheckable(True)
263
             action.setData(str(ct.consumption_type_id))
269
             action.setData(str(ct.consumption_type_id))
264
 
270
 
319
     def add_consumption_type(self) -> None:
325
     def add_consumption_type(self) -> None:
320
         self.show_keyboard()
326
         self.show_keyboard()
321
         name, ok = QInputDialog.getItem(
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
         self.hide_keyboard()
330
         self.hide_keyboard()
328
 
331
 
331
             ct = ct.create()
334
             ct = ct.create()
332
 
335
 
333
             action = QAction(
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
             action.setCheckable(True)
339
             action.setCheckable(True)
339
             action.setData(str(ct.consumption_type_id))
340
             action.setData(str(ct.consumption_type_id))
379
         self.undo_queue.append(consumption)
380
         self.undo_queue.append(consumption)
380
         self.undo_action.setDisabled(False)
381
         self.undo_action.setDisabled(False)
381
 
382
 
382
-
383
     @staticmethod
383
     @staticmethod
384
     def create_spacer() -> QWidget:
384
     def create_spacer() -> QWidget:
385
         """ Return an empty QWidget that automatically expands. """
385
         """ Return an empty QWidget that automatically expands. """
394
         directory. """
394
         directory. """
395
 
395
 
396
         if self.dark_theme:
396
         if self.dark_theme:
397
-            filename = 'white_' + filename
397
+            filename = "white_" + filename
398
 
398
 
399
         icon = QIcon(os.path.join(self.icons_dir, filename))
399
         icon = QIcon(os.path.join(self.icons_dir, filename))
400
         return icon
400
         return icon
402
 
402
 
403
 def main() -> None:
403
 def main() -> None:
404
     """ Main entry point of GUI client. """
404
     """ Main entry point of GUI client. """
405
+    LOG.info("Loading piket_client")
405
     app = QApplication(sys.argv)
406
     app = QApplication(sys.argv)
407
+
408
+    # Set dark theme
406
     app.setStyleSheet(qdarkstyle.load_stylesheet_pyside2())
409
     app.setStyleSheet(qdarkstyle.load_stylesheet_pyside2())
410
+
411
+    # Enlarge font size
407
     font = app.font()
412
     font = app.font()
408
     size = font.pointSize()
413
     size = font.pointSize()
409
     font.setPointSize(size * 1.5)
414
     font.setPointSize(size * 1.5)
410
     app.setFont(font)
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
     main_window = PiketMainWindow()
431
     main_window = PiketMainWindow()
413
     main_window.show()
432
     main_window.show()
414
 
433
 
434
+    # Let's go
435
+    LOG.info("Starting QT event loop.")
415
     app.exec_()
436
     app.exec_()
416
 
437
 
417
 
438
 

+ 49 - 6
piket_client/logger.py

1
 import os.path
1
 import os.path
2
 import logging
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
 Provides access to the models stored in the database, via the server.
2
 Provides access to the models stored in the database, via the server.
3
 """
3
 """
4
 import datetime
4
 import datetime
5
+import logging
5
 from typing import NamedTuple
6
 from typing import NamedTuple
6
 from urllib.parse import urljoin
7
 from urllib.parse import urljoin
7
 
8
 
8
 import requests
9
 import requests
9
 
10
 
10
-from . import logger
11
 
11
 
12
-LOG = logger.getLogger("model")
12
+LOG = logging.getLogger(__name__)
13
 
13
 
14
 SERVER_URL = "http://127.0.0.1:5000"
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
 class Person(NamedTuple):
33
 class Person(NamedTuple):
18
     """ Represents a Person, as retrieved from the database. """
34
     """ Represents a Person, as retrieved from the database. """
19
 
35
 
73
         return Person.from_dict(data["person"])
89
         return Person.from_dict(data["person"])
74
 
90
 
75
     def set_active(self, new_state=True) -> "Person":
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
         try:
97
         try:
80
             data = req.json()
98
             data = req.json()
92
 
110
 
93
         return Person.from_dict(data["person"])
111
         return Person.from_dict(data["person"])
94
 
112
 
95
-
96
-
97
     @classmethod
113
     @classmethod
98
     def get(cls, person_id: int) -> "Person":
114
     def get(cls, person_id: int) -> "Person":
99
         """ Retrieve a Person by id. """
115
         """ Retrieve a Person by id. """
122
     def get_all(cls, active=None) -> ["Person"]:
138
     def get_all(cls, active=None) -> ["Person"]:
123
         """ Get all active People. """
139
         """ Get all active People. """
124
         active = int(active)
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
         try:
143
         try:
129
             data = req.json()
144
             data = req.json()
260
     @classmethod
275
     @classmethod
261
     def from_dict(cls, data: dict) -> "Consumption":
276
     def from_dict(cls, data: dict) -> "Consumption":
262
         """ Reconstruct a Consumption from a dict. """
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
         # 2018-08-31T17:30:47.871521
279
         # 2018-08-31T17:30:47.871521
265
         return cls(
280
         return cls(
266
             consumption_id=data["consumption_id"],
281
             consumption_id=data["consumption_id"],
267
             person_id=data["person_id"],
282
             person_id=data["person_id"],
268
             consumption_type_id=data["consumption_type_id"],
283
             consumption_type_id=data["consumption_type_id"],
269
             settlement_id=data["settlement_id"],
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
             reversed=data["reversed"],
286
             reversed=data["reversed"],
273
         )
287
         )
274
 
288
 

+ 6 - 5
piket_server/__init__.py

138
     """ Return a list of currently known people. """
138
     """ Return a list of currently known people. """
139
     people = Person.query.order_by(Person.name).all()
139
     people = Person.query.order_by(Person.name).all()
140
     q = Person.query.order_by(Person.name)
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
         q = q.filter_by(active=active_status)
143
         q = q.filter_by(active=active_status)
144
     people = q.all()
144
     people = q.all()
145
     result = [person.as_dict for person in people]
145
     result = [person.as_dict for person in people]
195
 
195
 
196
     return jsonify(person=person.as_dict, consumption=consumption.as_dict), 201
196
     return jsonify(person=person.as_dict, consumption=consumption.as_dict), 201
197
 
197
 
198
+
198
 @app.route("/people/<int:person_id>", methods=["PATCH"])
199
 @app.route("/people/<int:person_id>", methods=["PATCH"])
199
 def update_person(person_id: int):
200
 def update_person(person_id: int):
200
     person = Person.query.get_or_404(person_id)
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
         db.session.add(person)
208
         db.session.add(person)
208
         db.session.commit()
209
         db.session.commit()

+ 6 - 7
piket_server/seed.py

21
     parser_clear.add_argument("--removemydata", action="store_true")
21
     parser_clear.add_argument("--removemydata", action="store_true")
22
 
22
 
23
     # Parser load_seeds
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
     parser_load_persons.set_defaults(func=cmd_load_persons)
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
     args = parser.parse_args()
30
     args = parser.parse_args()
30
     args.func(args)
31
     args.func(args)
62
 
63
 
63
     print("Aborting.")
64
     print("Aborting.")
64
 
65
 
66
+
65
 def cmd_load_persons(args) -> None:
67
 def cmd_load_persons(args) -> None:
66
     """ Entrypoint for 'load_persons" subcommand. """
68
     """ Entrypoint for 'load_persons" subcommand. """
67
 
69
 
71
         reader = csv.DictReader(csvfile)
73
         reader = csv.DictReader(csvfile)
72
 
74
 
73
         for row in reader:
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
             db.session.add(p)
77
             db.session.add(p)
79
 
78
 
80
     db.session.commit()
79
     db.session.commit()

+ 3 - 0
setup.py

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