tradedangerous 12.7.6__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.
- py.typed +1 -0
- trade.py +49 -0
- tradedangerous/__init__.py +43 -0
- tradedangerous/cache.py +1381 -0
- tradedangerous/cli.py +136 -0
- tradedangerous/commands/TEMPLATE.py +74 -0
- tradedangerous/commands/__init__.py +244 -0
- tradedangerous/commands/buildcache_cmd.py +102 -0
- tradedangerous/commands/buy_cmd.py +427 -0
- tradedangerous/commands/commandenv.py +372 -0
- tradedangerous/commands/exceptions.py +94 -0
- tradedangerous/commands/export_cmd.py +150 -0
- tradedangerous/commands/import_cmd.py +222 -0
- tradedangerous/commands/local_cmd.py +243 -0
- tradedangerous/commands/market_cmd.py +207 -0
- tradedangerous/commands/nav_cmd.py +252 -0
- tradedangerous/commands/olddata_cmd.py +270 -0
- tradedangerous/commands/parsing.py +221 -0
- tradedangerous/commands/rares_cmd.py +298 -0
- tradedangerous/commands/run_cmd.py +1521 -0
- tradedangerous/commands/sell_cmd.py +262 -0
- tradedangerous/commands/shipvendor_cmd.py +60 -0
- tradedangerous/commands/station_cmd.py +68 -0
- tradedangerous/commands/trade_cmd.py +181 -0
- tradedangerous/commands/update_cmd.py +67 -0
- tradedangerous/corrections.py +55 -0
- tradedangerous/csvexport.py +234 -0
- tradedangerous/db/__init__.py +27 -0
- tradedangerous/db/adapter.py +192 -0
- tradedangerous/db/config.py +107 -0
- tradedangerous/db/engine.py +259 -0
- tradedangerous/db/lifecycle.py +332 -0
- tradedangerous/db/locks.py +208 -0
- tradedangerous/db/orm_models.py +500 -0
- tradedangerous/db/paths.py +113 -0
- tradedangerous/db/utils.py +661 -0
- tradedangerous/edscupdate.py +565 -0
- tradedangerous/edsmupdate.py +474 -0
- tradedangerous/formatting.py +210 -0
- tradedangerous/fs.py +156 -0
- tradedangerous/gui.py +1146 -0
- tradedangerous/mapping.py +133 -0
- tradedangerous/mfd/__init__.py +103 -0
- tradedangerous/mfd/saitek/__init__.py +3 -0
- tradedangerous/mfd/saitek/directoutput.py +678 -0
- tradedangerous/mfd/saitek/x52pro.py +195 -0
- tradedangerous/misc/checkpricebounds.py +287 -0
- tradedangerous/misc/clipboard.py +49 -0
- tradedangerous/misc/coord64.py +83 -0
- tradedangerous/misc/csvdialect.py +57 -0
- tradedangerous/misc/derp-sentinel.py +35 -0
- tradedangerous/misc/diff-system-csvs.py +159 -0
- tradedangerous/misc/eddb.py +81 -0
- tradedangerous/misc/eddn.py +349 -0
- tradedangerous/misc/edsc.py +437 -0
- tradedangerous/misc/edsm.py +121 -0
- tradedangerous/misc/importeddbstats.py +54 -0
- tradedangerous/misc/prices-json-exp.py +179 -0
- tradedangerous/misc/progress.py +194 -0
- tradedangerous/plugins/__init__.py +249 -0
- tradedangerous/plugins/edcd_plug.py +371 -0
- tradedangerous/plugins/eddblink_plug.py +861 -0
- tradedangerous/plugins/edmc_batch_plug.py +133 -0
- tradedangerous/plugins/spansh_plug.py +2647 -0
- tradedangerous/prices.py +211 -0
- tradedangerous/submit-distances.py +422 -0
- tradedangerous/templates/Added.csv +37 -0
- tradedangerous/templates/Category.csv +17 -0
- tradedangerous/templates/RareItem.csv +143 -0
- tradedangerous/templates/TradeDangerous.sql +338 -0
- tradedangerous/tools.py +40 -0
- tradedangerous/tradecalc.py +1302 -0
- tradedangerous/tradedb.py +2320 -0
- tradedangerous/tradeenv.py +313 -0
- tradedangerous/tradeenv.pyi +109 -0
- tradedangerous/tradeexcept.py +131 -0
- tradedangerous/tradeorm.py +183 -0
- tradedangerous/transfers.py +192 -0
- tradedangerous/utils.py +243 -0
- tradedangerous/version.py +16 -0
- tradedangerous-12.7.6.dist-info/METADATA +106 -0
- tradedangerous-12.7.6.dist-info/RECORD +87 -0
- tradedangerous-12.7.6.dist-info/WHEEL +5 -0
- tradedangerous-12.7.6.dist-info/entry_points.txt +3 -0
- tradedangerous-12.7.6.dist-info/licenses/LICENSE +373 -0
- tradedangerous-12.7.6.dist-info/top_level.txt +2 -0
- tradegui.py +24 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# tradedangerous/db/locks.py
|
|
2
|
+
# -----------------------------------------------------------------------------
|
|
3
|
+
# Advisory lock helpers (MariaDB/MySQL) — per-station serialization
|
|
4
|
+
#
|
|
5
|
+
# SQLite compatibility:
|
|
6
|
+
# - On SQLite (or any unsupported dialect), all helpers become NO-OPs and
|
|
7
|
+
# behave as if the lock was immediately acquired (yield True). This lets
|
|
8
|
+
# shared code run unchanged across backends.
|
|
9
|
+
#
|
|
10
|
+
# Usage (both writers must use the SAME key format):
|
|
11
|
+
# from tradedangerous.db.locks import station_advisory_lock
|
|
12
|
+
#
|
|
13
|
+
# with sa_session_local(session_factory) as s:
|
|
14
|
+
# # (optional) set isolation once per process elsewhere:
|
|
15
|
+
# # s.execute(text("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")); s.commit()
|
|
16
|
+
# with station_advisory_lock(s, station_id, timeout_seconds=0.2, max_retries=4) as got:
|
|
17
|
+
# if not got:
|
|
18
|
+
# # processor: defer/requeue work for this station and continue
|
|
19
|
+
# return
|
|
20
|
+
# with s.begin():
|
|
21
|
+
# # do per-station writes here...
|
|
22
|
+
# pass
|
|
23
|
+
# -----------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import time
|
|
28
|
+
from contextlib import contextmanager
|
|
29
|
+
from typing import Iterator
|
|
30
|
+
|
|
31
|
+
from sqlalchemy import text
|
|
32
|
+
from sqlalchemy.orm import Session
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"station_advisory_lock",
|
|
36
|
+
"acquire_station_lock",
|
|
37
|
+
"release_station_lock",
|
|
38
|
+
"station_lock_key",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# Precompiled SQL (MySQL/MariaDB only)
|
|
42
|
+
_SQL_GET_LOCK = text("SELECT GET_LOCK(:k, :t)")
|
|
43
|
+
_SQL_RELEASE_LOCK = text("SELECT RELEASE_LOCK(:k)")
|
|
44
|
+
|
|
45
|
+
def _is_lock_supported(session: Session) -> bool:
|
|
46
|
+
"""
|
|
47
|
+
Return True if the current SQLAlchemy session is bound to a backend that
|
|
48
|
+
supports advisory locks via GET_LOCK/RELEASE_LOCK (MySQL/MariaDB).
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
name = (session.get_bind().dialect.name or "").lower()
|
|
52
|
+
except Exception:
|
|
53
|
+
name = ""
|
|
54
|
+
return name in ("mysql", "mariadb")
|
|
55
|
+
|
|
56
|
+
def _ensure_read_committed(session: Session) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Ensure the session is using READ COMMITTED for subsequent transactions.
|
|
59
|
+
- Applies only to MySQL/MariaDB.
|
|
60
|
+
- No-ops on SQLite/others.
|
|
61
|
+
- Only sets it if NOT already inside a transaction (affects next txn).
|
|
62
|
+
"""
|
|
63
|
+
if not _is_lock_supported(session):
|
|
64
|
+
return
|
|
65
|
+
try:
|
|
66
|
+
# Only set if we're not already in a transaction; otherwise it would
|
|
67
|
+
# affect the next transaction, not the current one.
|
|
68
|
+
if not session.in_transaction():
|
|
69
|
+
session.execute(text("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"))
|
|
70
|
+
# No explicit commit needed; this is a session-level setting.
|
|
71
|
+
except Exception:
|
|
72
|
+
# Best-effort; if this fails we just proceed with the default isolation.
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
def station_lock_key(station_id: int) -> str:
|
|
76
|
+
"""
|
|
77
|
+
Return the advisory lock key used by both writers for the same station.
|
|
78
|
+
Keep this format identical in all writers (processor + Spansh).
|
|
79
|
+
"""
|
|
80
|
+
return f"td.station.{int(station_id)}"
|
|
81
|
+
|
|
82
|
+
def acquire_station_lock(session: Session, station_id: int, timeout_seconds: float) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Try to acquire the advisory lock for a station on THIS DB connection.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
True -> acquired within timeout (or NO-OP True on unsupported dialects)
|
|
88
|
+
False -> timed out (lock held elsewhere)
|
|
89
|
+
|
|
90
|
+
Notes:
|
|
91
|
+
- Advisory locks are per-connection. Use the same Session for acquire,
|
|
92
|
+
the critical section, and release.
|
|
93
|
+
- On SQLite/unsupported dialects, this is a NO-OP that returns True.
|
|
94
|
+
"""
|
|
95
|
+
if not _is_lock_supported(session):
|
|
96
|
+
return True # NO-OP on SQLite/unsupported backends
|
|
97
|
+
|
|
98
|
+
key = station_lock_key(station_id)
|
|
99
|
+
row = session.execute(_SQL_GET_LOCK, {"k": key, "t": float(timeout_seconds)}).first()
|
|
100
|
+
# MariaDB/MySQL GET_LOCK returns 1 (acquired), 0 (timeout), or NULL (error)
|
|
101
|
+
return bool(row and row[0] == 1)
|
|
102
|
+
|
|
103
|
+
def release_station_lock(session: Session, station_id: int) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Release the advisory lock for a station on THIS DB connection.
|
|
106
|
+
Safe to call in finally; releasing a non-held lock is harmless.
|
|
107
|
+
|
|
108
|
+
On SQLite/unsupported dialects, this is a NO-OP.
|
|
109
|
+
"""
|
|
110
|
+
if not _is_lock_supported(session):
|
|
111
|
+
return # NO-OP on SQLite/unsupported backends
|
|
112
|
+
|
|
113
|
+
key = station_lock_key(station_id)
|
|
114
|
+
try:
|
|
115
|
+
session.execute(_SQL_RELEASE_LOCK, {"k": key})
|
|
116
|
+
except Exception:
|
|
117
|
+
# Intentionally swallow — RELEASE_LOCK may return 0/NULL if not held.
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
@contextmanager
|
|
121
|
+
def station_advisory_lock(
|
|
122
|
+
session: Session,
|
|
123
|
+
station_id: int,
|
|
124
|
+
timeout_seconds: float = 0.2,
|
|
125
|
+
max_retries: int = 4,
|
|
126
|
+
backoff_start_seconds: float = 0.05,
|
|
127
|
+
) -> Iterator[bool]:
|
|
128
|
+
"""
|
|
129
|
+
Context manager to acquire/retry/release a per-station advisory lock.
|
|
130
|
+
|
|
131
|
+
Deadlock-safety requirement:
|
|
132
|
+
- Do NOT release the advisory lock before the station's writes are COMMITTED.
|
|
133
|
+
- Previously we only committed when this helper created the transaction.
|
|
134
|
+
If the Session already had an active transaction (SQLAlchemy autobegin),
|
|
135
|
+
the lock could be released while row locks were still pending commit.
|
|
136
|
+
|
|
137
|
+
Behaviour:
|
|
138
|
+
- On MySQL/MariaDB: tries GET_LOCK() with bounded retries + exponential backoff.
|
|
139
|
+
- If acquired (got=True): COMMIT on normal exit BEFORE releasing the advisory lock,
|
|
140
|
+
regardless of whether this helper started the transaction.
|
|
141
|
+
- If NOT acquired (got=False) and this helper started the transaction: ROLLBACK to
|
|
142
|
+
avoid leaving an idle open transaction pinned to a connection.
|
|
143
|
+
- If an exception escapes the caller's block: ROLLBACK (best-effort) then re-raise.
|
|
144
|
+
- On unsupported dialects (e.g. SQLite): yields True and does nothing.
|
|
145
|
+
|
|
146
|
+
WARNING:
|
|
147
|
+
- Do not wrap this context manager inside an external transaction manager
|
|
148
|
+
(e.g. `with session.begin():`) because it may COMMIT inside that scope.
|
|
149
|
+
"""
|
|
150
|
+
# Fast-path NO-OP for SQLite/unsupported dialects
|
|
151
|
+
if not _is_lock_supported(session):
|
|
152
|
+
yield True
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# Prefer READ COMMITTED to reduce lock contention (best-effort).
|
|
156
|
+
_ensure_read_committed(session)
|
|
157
|
+
|
|
158
|
+
started_txn = False
|
|
159
|
+
txn_ctx = None
|
|
160
|
+
if not session.in_transaction():
|
|
161
|
+
# Pin lock + DML to the same connection by opening a txn.
|
|
162
|
+
txn_ctx = session.begin()
|
|
163
|
+
started_txn = True
|
|
164
|
+
|
|
165
|
+
got = False
|
|
166
|
+
try:
|
|
167
|
+
attempt = 0
|
|
168
|
+
while attempt < max_retries:
|
|
169
|
+
if acquire_station_lock(session, station_id, timeout_seconds):
|
|
170
|
+
got = True
|
|
171
|
+
break
|
|
172
|
+
time.sleep(backoff_start_seconds * (2 ** attempt))
|
|
173
|
+
attempt += 1
|
|
174
|
+
|
|
175
|
+
# Hand control to caller
|
|
176
|
+
yield got
|
|
177
|
+
|
|
178
|
+
if got:
|
|
179
|
+
# Commit while the advisory lock is still held.
|
|
180
|
+
if session.in_transaction():
|
|
181
|
+
session.commit()
|
|
182
|
+
else:
|
|
183
|
+
# If we opened a txn just to attempt locking, close it out cleanly.
|
|
184
|
+
if started_txn and session.in_transaction():
|
|
185
|
+
session.rollback()
|
|
186
|
+
|
|
187
|
+
except Exception:
|
|
188
|
+
# Ensure we don't leak row locks / open txn on error.
|
|
189
|
+
if session.in_transaction():
|
|
190
|
+
try:
|
|
191
|
+
session.rollback()
|
|
192
|
+
except Exception:
|
|
193
|
+
pass
|
|
194
|
+
raise
|
|
195
|
+
|
|
196
|
+
finally:
|
|
197
|
+
# Release advisory lock after commit/rollback decisions above.
|
|
198
|
+
if got:
|
|
199
|
+
try:
|
|
200
|
+
release_station_lock(session, station_id)
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
if started_txn and txn_ctx is not None:
|
|
205
|
+
try:
|
|
206
|
+
txn_ctx.close()
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|