Repository navigation
Expand file tree
/
Copy pathdatabase_file.py
More file actions
202 lines (160 loc) · 4.33 KB
/
Copy pathdatabase_file.py
File metadata and controls
202 lines (160 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import threading
import time
import psycopg
from assets.event_queue_file import DB_QUEUE
from config import (
POSTGRES_DB,
POSTGRES_HOST,
POSTGRES_PASSWORD,
POSTGRES_PORT,
POSTGRES_USER,
)
DB_INSERT_STATE = None
DATABASE_RETRY_SECONDS = 5
# Solo lo utiliza el hilo database-writer.
_connection = None
# Referencia al hilo para impedir arrancarlo dos veces.
_writer_thread = None
# Protege únicamente el arranque del hilo.
_start_lock = threading.Lock()
def create_database_connection():
"""
Crea una conexión PostgreSQL nueva.
autocommit=True confirma cada INSERT
individualmente.
"""
connection = psycopg.connect(
host=POSTGRES_HOST,
port=POSTGRES_PORT,
dbname=POSTGRES_DB,
user=POSTGRES_USER,
password=POSTGRES_PASSWORD,
connect_timeout=5,
autocommit=True,
application_name="suzdalenko-opcua",
)
return connection
def close_database_connection():
"""
Cierra y elimina la conexión actual.
"""
global _connection
if _connection is not None:
try:
_connection.close()
except Exception:
pass
_connection = None
def get_database_connection():
"""
Devuelve siempre la misma conexión.
Si todavía no existe o se cerró, crea otra.
"""
global _connection
if (
_connection is None
or _connection.closed
):
_connection = create_database_connection()
return _connection
def insert_pesadora_line(db_line):
"""
Inserta una línea en public.pesadora_lineas.
Devuelve:
id si se insertó.
None si ya existía inicio_of + kg.
"""
sql = """
INSERT INTO public.pesadora_lineas (
art_erp,
art_name,
lote,
batch,
inicio_of,
fin_of,
bolsas_buenas,
kg,
bolsas_total,
peso_medio,
date
)
VALUES (
%s,
%s,
%s,
%s,
%s,
%s,
%s,
%s,
%s,
%s,
%s
)
ON CONFLICT ON CONSTRAINT
pesadora_lineas_inicio_of_kg_unique
DO NOTHING
RETURNING id
"""
values = (
str(db_line["art_erp"]),
str(db_line["art_name"]),
str(db_line["lote"]),
str(db_line["batch"]),
str(db_line["inicio_of"]),
str(db_line["fin_of"]),
float(db_line["bolsas_buenas"]),
float(db_line["kg"]),
float(db_line["bolsas_total"]),
float(db_line["peso_medio"]),
str(db_line["date"]),
)
connection = get_database_connection()
with connection.cursor() as cursor:
cursor.execute(
sql,
values,
)
row = cursor.fetchone()
# ON CONFLICT DO NOTHING no devuelve ninguna fila.
if row is None:
return None
return row[0]
def database_writer_loop():
"""
Consume permanentemente DB_QUEUE.
Si PostgreSQL falla, mantiene la línea actual
y vuelve a intentar guardarla.
"""
global DB_INSERT_STATE
while True:
db_line = DB_QUEUE.get()
try:
while True:
try:
inserted_id = insert_pesadora_line(
db_line
)
if inserted_id is None:
DB_INSERT_STATE = 'Línea PostgreSQL duplicada'
else:
DB_INSERT_STATE = 'ok'
# La línea ya fue insertada o era duplicada.
break
except Exception as error:
DB_INSERT_STATE = f"ERROR PostgreSQL {str(error)} "
close_database_connection()
time.sleep(DATABASE_RETRY_SECONDS)
finally:
DB_QUEUE.task_done()
def start_database_writer():
"""
Arranca el hilo PostgreSQL una sola vez.
"""
global _writer_thread
with _start_lock:
if _writer_thread is not None and _writer_thread.is_alive():
return _writer_thread
_writer_thread = threading.Thread(target=database_writer_loop, name="database-writer", daemon=True,)
_writer_thread.start()
return _writer_thread