simo 3.1.3__py3-none-any.whl → 3.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of simo might be problematic. Click here for more details.

@@ -1,19 +1,73 @@
1
1
  from django.contrib.gis.db.backends.postgis.base import (
2
2
  DatabaseWrapper as PostGisPsycopg2DatabaseWrapper
3
3
  )
4
- from django.db.utils import OperationalError, InterfaceError
4
+ """Custom PostGIS database wrapper with light self-healing.
5
+
6
+ We retry cursor creation on transient driver-level errors. Previously we
7
+ were catching Django's wrapper exceptions (django.db.utils.InterfaceError/
8
+ OperationalError). However, those are only raised by the outer
9
+ `wrap_database_errors` context. Inside `create_cursor()` the driver
10
+ (`psycopg2`) raises its own exceptions, so our except block never ran and
11
+ connections weren't healed.
12
+
13
+ By catching psycopg2 errors here (in addition to Django's), we ensure
14
+ we can close() and reconnect() this connection when `self.connection`
15
+ is already closed or becomes unusable, avoiding busy error loops in
16
+ MQTT threads and periodic tasks.
17
+ """
18
+
19
+ from django.db.utils import OperationalError as DjangoOperationalError, InterfaceError as DjangoInterfaceError
20
+ try:
21
+ # Catch driver-level errors where they originate.
22
+ from psycopg2 import OperationalError as PsycopgOperationalError, InterfaceError as PsycopgInterfaceError
23
+ except Exception: # pragma: no cover - very defensive
24
+ PsycopgOperationalError = PsycopgInterfaceError = Exception
5
25
  from django.utils.asyncio import async_unsafe
26
+ import time
27
+ import random
6
28
 
7
29
 
8
30
  class DatabaseWrapper(PostGisPsycopg2DatabaseWrapper):
9
31
  @async_unsafe
10
32
  def create_cursor(self, name=None):
33
+ """Create a DB cursor with a single, simple heal-once path.
34
+
35
+ - Fast path: return cursor immediately.
36
+ - On error: if it's a connectivity issue (known exceptions or
37
+ connection.closed set), close + backoff + reconnect, then retry once.
38
+ - Otherwise: re-raise the original exception.
39
+ """
11
40
  try:
12
41
  return super().create_cursor(name=name)
13
- except (InterfaceError, OperationalError):
14
- # Heal this very connection
15
- self.close()
16
- self.connect()
17
- return super().create_cursor(name=name)
42
+ except Exception as e:
43
+ # Determine if this is a connectivity problem
44
+ is_connectivity_err = isinstance(
45
+ e,
46
+ (
47
+ DjangoInterfaceError,
48
+ DjangoOperationalError,
49
+ PsycopgInterfaceError,
50
+ PsycopgOperationalError,
51
+ ),
52
+ )
53
+ if not is_connectivity_err:
54
+ try:
55
+ conn = getattr(self, 'connection', None)
56
+ is_connectivity_err = bool(getattr(conn, 'closed', 0))
57
+ except Exception:
58
+ is_connectivity_err = False
18
59
 
60
+ if not is_connectivity_err:
61
+ # Not a connection issue; bubble up unchanged
62
+ raise
19
63
 
64
+ # Heal this very connection and retry once
65
+ try:
66
+ self.close()
67
+ finally:
68
+ try:
69
+ time.sleep(0.05 + random.random() * 0.15) # 50–200 ms
70
+ except Exception:
71
+ pass
72
+ self.connect()
73
+ return super().create_cursor(name=name)
@@ -81,12 +81,12 @@ stopsignal=INT
81
81
  environment=PYTHONUNBUFFERED=1
82
82
 
83
83
 
84
- [program:simo-app-mqtt]
84
+ [program:simo-mqtt-control]
85
85
  directory={{ project_dir }}/hub/
86
86
  command={{ venv_path }}/python manage.py run_app_mqtt_control
87
87
  process_name=%(program_name)s
88
88
  user=root
89
- stdout_logfile=/var/log/simo/app_mqtt.log
89
+ stdout_logfile=/var/log/simo/mqtt_control.log
90
90
  stdout_logfile_maxbytes=1MB
91
91
  stdout_logfile_backups=3
92
92
  redirect_stderr=true
@@ -97,12 +97,12 @@ killasgroup=true
97
97
  stopsignal=INT
98
98
  environment=PYTHONUNBUFFERED=1
99
99
 
100
- [program:simo-app-mqtt-fanout]
100
+ [program:simo-mqtt-fanout]
101
101
  directory={{ project_dir }}/hub/
102
102
  command={{ venv_path }}/python manage.py run_app_mqtt_fanout
103
103
  process_name=%(program_name)s
104
104
  user=root
105
- stdout_logfile=/var/log/simo/app_mqtt_fanout.log
105
+ stdout_logfile=/var/log/simo/mqtt_fanout.log
106
106
  stdout_logfile_maxbytes=1MB
107
107
  stdout_logfile_backups=3
108
108
  redirect_stderr=true
@@ -21,10 +21,18 @@ class Command(BaseCommand):
21
21
  client.username_pw_set('root', settings.SECRET_KEY)
22
22
  client.on_connect = self.on_connect
23
23
  client.on_message = self.on_message
24
+ client.on_disconnect = self.on_disconnect
25
+ # Back off on reconnects to avoid busy-spin during outages
26
+ client.reconnect_delay_set(min_delay=1, max_delay=30)
27
+ # Route Paho logs to Python logging for visibility
28
+ try:
29
+ client.enable_logger()
30
+ except Exception:
31
+ pass
24
32
  client.connect(host=settings.MQTT_HOST, port=settings.MQTT_PORT)
25
33
  try:
26
- while True:
27
- client.loop()
34
+ # Blocking network loop with built-in reconnect/backoff
35
+ client.loop_forever(retry_first_connection=True)
28
36
  finally:
29
37
  try:
30
38
  client.disconnect()
@@ -118,6 +126,14 @@ class Command(BaseCommand):
118
126
  # Never crash the consumer
119
127
  pass
120
128
 
129
+ def on_disconnect(self, client, userdata, rc):
130
+ # Non-zero rc means unexpected disconnect. Paho will back off and retry.
131
+ if rc != 0:
132
+ try:
133
+ print(f"Control MQTT disconnect rc={rc}; reconnecting with backoff...", file=sys.stderr)
134
+ except Exception:
135
+ pass
136
+
121
137
  def respond(self, client, user_id, request_id, ok=True, result=None, error=None):
122
138
  if not request_id:
123
139
  return
@@ -21,10 +21,18 @@ class Command(BaseCommand):
21
21
  self.client.username_pw_set('root', settings.SECRET_KEY)
22
22
  self.client.on_connect = self.on_connect
23
23
  self.client.on_message = self.on_message
24
+ self.client.on_disconnect = self.on_disconnect
25
+ # Back off on reconnects to avoid busy-spin during outages
26
+ self.client.reconnect_delay_set(min_delay=1, max_delay=30)
27
+ # Route Paho logs to Python logging for visibility
28
+ try:
29
+ self.client.enable_logger()
30
+ except Exception:
31
+ pass
24
32
  self.client.connect(host=settings.MQTT_HOST, port=settings.MQTT_PORT)
25
33
  try:
26
- while True:
27
- self.client.loop()
34
+ # Blocking network loop with built-in reconnect/backoff
35
+ self.client.loop_forever(retry_first_connection=True)
28
36
  finally:
29
37
  try:
30
38
  self.client.disconnect()
@@ -95,3 +103,11 @@ class Command(BaseCommand):
95
103
  except Exception:
96
104
  # Never crash the consumer
97
105
  print('Fanout error:', ''.join(traceback.format_exception(*sys.exc_info())), file=sys.stderr)
106
+
107
+ def on_disconnect(self, client, userdata, rc):
108
+ # Non-zero rc means unexpected disconnect. Paho will back off and retry.
109
+ if rc != 0:
110
+ try:
111
+ print(f"Fanout MQTT disconnect rc={rc}; reconnecting with backoff...", file=sys.stderr)
112
+ except Exception:
113
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simo
3
- Version: 3.1.3
3
+ Version: 3.1.5
4
4
  Summary: Smart Home Supremacy
5
5
  Author-email: "Simon V." <simon@simo.io>
6
6
  Project-URL: Homepage, https://simo.io
@@ -191,7 +191,7 @@ simo/core/__pycache__/views.cpython-38.pyc,sha256=kYKvEcyKicdkTcN0iEJ4pT101-KHiZ
191
191
  simo/core/__pycache__/widgets.cpython-312.pyc,sha256=f_AiYFfA7Wl4Wm9FqUOlluSGLAN_nkLyDUsKpkScsXg,4447
192
192
  simo/core/__pycache__/widgets.cpython-38.pyc,sha256=sR0ZeHCHrhnNDBJuRrxp3zUsfBp0xrtF0xrK2TkQv1o,3520
193
193
  simo/core/db_backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
194
- simo/core/db_backend/base.py,sha256=4vl0Nx8PcBG5C8QQpAQPFu-TKH8Hs6R5OmzJqIiLibQ,598
194
+ simo/core/db_backend/base.py,sha256=fGagw119bvP3Ns3NFvIDlrlNSa9-LLt2HMuIjnB9h9g,2933
195
195
  simo/core/db_backend/__pycache__/__init__.cpython-312.pyc,sha256=Brq4UBCggJrt590OFV3VmgARJYRK902FnnFFFK_Azng,133
196
196
  simo/core/db_backend/__pycache__/__init__.cpython-38.pyc,sha256=sxC6PmFqnwe6RRKzSR7QtUFzRc_aRRR1fffsR3TPLRc,169
197
197
  simo/core/db_backend/__pycache__/base.cpython-312.pyc,sha256=ykKrTOC28v2cl0C--bqeLEaydHS5CG1RFGL0lon53HU,1135
@@ -286,7 +286,7 @@ simo/core/management/_hub_template/hub/celeryc.py,sha256=3ksDXftIZKJ4Cq9WNKJERdZ
286
286
  simo/core/management/_hub_template/hub/manage.py,sha256=PNNlw3EVeIJDgkG0l-klqoxsKWfTYWG9jzRG0upmAaI,620
287
287
  simo/core/management/_hub_template/hub/nginx.conf,sha256=_-ch60oQYXMWmcvYUEbxMvXq9S46exwKmiBaVrxkQ3c,2339
288
288
  simo/core/management/_hub_template/hub/settings.py,sha256=4QhvhbtLRxHvAntwqG_qeAAtpDUqKvN4jzw9u3vqff8,361
289
- simo/core/management/_hub_template/hub/supervisor.conf,sha256=dWgBeLucl355Lw0MmM7HXVl7ZUBT-Mb8LwRyEjn2QOo,3647
289
+ simo/core/management/_hub_template/hub/supervisor.conf,sha256=g9Df8scz-FrcLJWSkFzwcEH9yfGR332W_BWXCxxpR_U,3647
290
290
  simo/core/management/_hub_template/hub/urls.py,sha256=Ydm-1BkYAzWeEF-MKSDIFf-7aE4qNLPm48-SA51XgJQ,25
291
291
  simo/core/management/_hub_template/hub/wsgi.py,sha256=Lo-huLHnMDTxSmMBOodVFMWBls9poddrV2KRzXU0xGo,280
292
292
  simo/core/management/_hub_template/hub/__pycache__/asgi.cpython-312.pyc,sha256=FVjb5whNF2mVopSz71s0Zms8d_b90_4smf84OWWkzDI,396
@@ -299,8 +299,8 @@ simo/core/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
299
299
  simo/core/management/commands/gateways_manager.py,sha256=oHzgC-eV4w_KHkiz6eCAlt3XMIwtS8_7mHDQ4UsU5y0,6967
300
300
  simo/core/management/commands/on_http_start.py,sha256=kxtWB3lOSyQA8tVmZRmLmaoVsliy5mhZp9_wwCvkj_A,6279
301
301
  simo/core/management/commands/republish_mqtt_state.py,sha256=mc8e7qnhDyC9_fiYr6d0g2s_3wGUXCrLo95-HBIrkOA,2248
302
- simo/core/management/commands/run_app_mqtt_control.py,sha256=oB0KbwDD_a08qwxo9jvKJJnCblzbrARr9xiQ6d54Hdc,4862
303
- simo/core/management/commands/run_app_mqtt_fanout.py,sha256=JHlRXDDsRYf72XzhV8uHcTh0-HlKYpk8f1uCw9hsHac,3861
302
+ simo/core/management/commands/run_app_mqtt_control.py,sha256=-eDDAXbGtjqN1OK7Ym2FIdP1wVNJYR0DdvIyRzmXPDc,5592
303
+ simo/core/management/commands/run_app_mqtt_fanout.py,sha256=diSEZM_sBQqm4wzzVgBT90Kt8kAhbZm5at2meEMv73M,4605
304
304
  simo/core/management/commands/run_gateway.py,sha256=bp0FQQoBeOSoxjHCCMicDL1fxPZZGyLgnq2QKht3bJo,645
305
305
  simo/core/management/commands/__pycache__/__init__.cpython-312.pyc,sha256=2Rw8IQ8vlr-wLXgjtc9zACkDD_SWZCxDlbrXw5vfI6o,142
306
306
  simo/core/management/commands/__pycache__/__init__.cpython-38.pyc,sha256=WKpfZZpAB9D7U4X6oWQIrU_H-6rUmq8Gl9fj9XaY2fw,178
@@ -11034,9 +11034,9 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
11034
11034
  simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11035
11035
  simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11036
11036
  simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11037
- simo-3.1.3.dist-info/licenses/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
11038
- simo-3.1.3.dist-info/METADATA,sha256=B7aSWj9iQ9HzHfD0b3mnLVPsfKtqGrpkkSHnTH8i-VM,2224
11039
- simo-3.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11040
- simo-3.1.3.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
11041
- simo-3.1.3.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
11042
- simo-3.1.3.dist-info/RECORD,,
11037
+ simo-3.1.5.dist-info/licenses/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
11038
+ simo-3.1.5.dist-info/METADATA,sha256=hXkJ2qxHLFHebhXd3yRy4Gvj2Bd4Vi2-ulumfeyYcIM,2224
11039
+ simo-3.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11040
+ simo-3.1.5.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
11041
+ simo-3.1.5.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
11042
+ simo-3.1.5.dist-info/RECORD,,
File without changes