tradedangerous 11.5.2__py3-none-any.whl → 12.0.0__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 tradedangerous might be problematic. Click here for more details.
- tradedangerous/cache.py +567 -395
- tradedangerous/cli.py +2 -2
- tradedangerous/commands/TEMPLATE.py +25 -26
- tradedangerous/commands/__init__.py +8 -16
- tradedangerous/commands/buildcache_cmd.py +40 -10
- tradedangerous/commands/buy_cmd.py +57 -46
- tradedangerous/commands/commandenv.py +0 -2
- tradedangerous/commands/export_cmd.py +78 -50
- tradedangerous/commands/import_cmd.py +70 -34
- tradedangerous/commands/market_cmd.py +52 -19
- tradedangerous/commands/olddata_cmd.py +120 -107
- tradedangerous/commands/rares_cmd.py +122 -110
- tradedangerous/commands/run_cmd.py +118 -66
- tradedangerous/commands/sell_cmd.py +52 -45
- tradedangerous/commands/shipvendor_cmd.py +49 -234
- tradedangerous/commands/station_cmd.py +55 -485
- tradedangerous/commands/update_cmd.py +56 -420
- tradedangerous/csvexport.py +173 -162
- tradedangerous/gui.py +2 -2
- tradedangerous/plugins/eddblink_plug.py +389 -252
- tradedangerous/plugins/spansh_plug.py +2488 -821
- tradedangerous/prices.py +124 -142
- tradedangerous/templates/TradeDangerous.sql +6 -6
- tradedangerous/tradecalc.py +1227 -1109
- tradedangerous/tradedb.py +533 -384
- tradedangerous/tradeenv.py +12 -1
- tradedangerous/version.py +1 -1
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/METADATA +17 -4
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/RECORD +33 -39
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/WHEEL +1 -1
- tradedangerous/commands/update_gui.py +0 -721
- tradedangerous/jsonprices.py +0 -254
- tradedangerous/plugins/edapi_plug.py +0 -1071
- tradedangerous/plugins/journal_plug.py +0 -537
- tradedangerous/plugins/netlog_plug.py +0 -316
- tradedangerous/templates/database_changes.json +0 -6
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/entry_points.txt +0 -0
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info/licenses}/LICENSE +0 -0
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/top_level.txt +0 -0
tradedangerous/csvexport.py
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
from sqlalchemy import inspect, text
|
|
2
3
|
from .tradeexcept import TradeException
|
|
3
4
|
|
|
5
|
+
from .db import utils as db_utils
|
|
6
|
+
|
|
7
|
+
|
|
4
8
|
import csv
|
|
5
9
|
import os
|
|
6
|
-
import sqlite3
|
|
7
10
|
|
|
8
11
|
######################################################################
|
|
9
12
|
# TradeDangerous :: Modules :: CSV Exporter
|
|
10
13
|
#
|
|
11
|
-
# Generate
|
|
14
|
+
# Generate CSV files for database tables.
|
|
12
15
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# * One column primary keys will be handled by the database engine
|
|
16
|
+
# Assumptions:
|
|
17
|
+
# * Each table has at most one UNIQUE index.
|
|
18
|
+
# * Referenced tables also have a UNIQUE index.
|
|
19
|
+
# * Only single-column foreign keys are supported.
|
|
20
|
+
# * Single-column primary keys are inferred automatically by SQLAlchemy.
|
|
19
21
|
#
|
|
20
|
-
|
|
21
|
-
# CAUTION: If the database structure gets changed this script might
|
|
22
|
-
# need some corrections.
|
|
22
|
+
# CAUTION: If the schema changes this module may require updates.
|
|
23
23
|
######################################################################
|
|
24
24
|
|
|
25
25
|
######################################################################
|
|
26
26
|
# Default values
|
|
27
27
|
|
|
28
|
-
#
|
|
28
|
+
# For some tables the first two columns will be reversed
|
|
29
29
|
reverseList = []
|
|
30
30
|
|
|
31
31
|
######################################################################
|
|
@@ -38,183 +38,194 @@ def search_keyList(items, val):
|
|
|
38
38
|
return row
|
|
39
39
|
return None
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
|
|
42
|
+
def getUniqueIndex(session, tableName):
|
|
43
|
+
"""Return all unique columns via SQLAlchemy inspector."""
|
|
44
|
+
inspector = inspect(session.get_bind())
|
|
44
45
|
unqIndex = []
|
|
45
|
-
for
|
|
46
|
-
if
|
|
47
|
-
|
|
48
|
-
unqCursor = conn.cursor()
|
|
49
|
-
for unqRow in unqCursor.execute("PRAGMA index_info('%s')" % idxRow['name']):
|
|
50
|
-
unqIndex.append(unqRow['name'])
|
|
46
|
+
for idx in inspector.get_indexes(tableName):
|
|
47
|
+
if idx.get("unique"):
|
|
48
|
+
unqIndex.extend(idx.get("column_names", []))
|
|
51
49
|
return unqIndex
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
|
|
52
|
+
def getFKeyList(session, tableName):
|
|
53
|
+
"""Return all single-column foreign keys via SQLAlchemy inspector."""
|
|
54
|
+
inspector = inspect(session.get_bind())
|
|
55
55
|
keyList = []
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if keyRow['seq'] == 1:
|
|
66
|
-
# if there is a second column, remove it from the list
|
|
67
|
-
keyList.remove( keyList[keyCount] )
|
|
68
|
-
keyCount -= 1
|
|
69
|
-
|
|
56
|
+
for fk in inspector.get_foreign_keys(tableName):
|
|
57
|
+
cols = fk.get("constrained_columns", [])
|
|
58
|
+
referred = fk.get("referred_columns", [])
|
|
59
|
+
if len(cols) == 1 and len(referred) == 1:
|
|
60
|
+
keyList.append({
|
|
61
|
+
"table": fk.get("referred_table"),
|
|
62
|
+
"from": cols[0],
|
|
63
|
+
"to": referred[0],
|
|
64
|
+
})
|
|
70
65
|
return keyList
|
|
71
66
|
|
|
72
|
-
|
|
67
|
+
|
|
68
|
+
def buildFKeyStmt(session, tableName, key):
|
|
73
69
|
"""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
Resolve the FK constraint against the UNIQUE index of the
|
|
71
|
+
referenced table.
|
|
72
|
+
|
|
73
|
+
Multicolumn UNIQUEs are allowed, but only the last column
|
|
74
|
+
may be treated as a single-column join target.
|
|
77
75
|
"""
|
|
78
|
-
unqIndex = getUniqueIndex(
|
|
79
|
-
keyList
|
|
76
|
+
unqIndex = getUniqueIndex(session, key["table"])
|
|
77
|
+
keyList = getFKeyList(session, key["table"])
|
|
80
78
|
keyStmt = []
|
|
79
|
+
|
|
81
80
|
for colName in unqIndex:
|
|
82
|
-
#
|
|
81
|
+
# If this unique column is itself a foreign key, recurse
|
|
83
82
|
keyKey = search_keyList(keyList, colName)
|
|
84
83
|
if keyKey:
|
|
85
|
-
|
|
86
|
-
for row in newStmt:
|
|
87
|
-
keyStmt.append(row)
|
|
84
|
+
keyStmt.extend(buildFKeyStmt(session, key["table"], keyKey))
|
|
88
85
|
else:
|
|
89
86
|
keyStmt.append({
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
"table": tableName,
|
|
88
|
+
"column": colName,
|
|
89
|
+
"joinTable": key["table"],
|
|
90
|
+
"joinColumn": key["to"],
|
|
94
91
|
})
|
|
95
|
-
|
|
92
|
+
|
|
96
93
|
return keyStmt
|
|
97
94
|
|
|
95
|
+
|
|
98
96
|
######################################################################
|
|
99
97
|
# Code
|
|
100
98
|
######################################################################
|
|
101
99
|
|
|
102
|
-
def exportTableToFile(
|
|
100
|
+
def exportTableToFile(tdb_or_session, tdenv, tableName, csvPath=None):
|
|
103
101
|
"""
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
Generate the CSV file for tableName in csvPath.
|
|
103
|
+
Returns (lineCount, exportPath).
|
|
104
|
+
|
|
105
|
+
Behaviour:
|
|
106
|
+
- Prefix unique columns with "unq:".
|
|
107
|
+
- Foreign keys are exported as "<col>@<joinTable>.<uniqueCol>".
|
|
108
|
+
- Datetime-like values for 'modified' columns are exported as
|
|
109
|
+
"YYYY-MM-DD HH:MM:SS" (no microseconds).
|
|
110
|
+
|
|
111
|
+
Compatible with either:
|
|
112
|
+
* a SQLAlchemy Session
|
|
113
|
+
* a TradeDB wrapper exposing .engine
|
|
106
114
|
"""
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
from sqlalchemy.orm import Session
|
|
116
|
+
|
|
117
|
+
# --- Resolve a SQLAlchemy session ---
|
|
118
|
+
if hasattr(tdb_or_session, "engine"):
|
|
119
|
+
# Likely a TradeDB instance
|
|
120
|
+
engine = tdb_or_session.engine
|
|
121
|
+
session = Session(engine)
|
|
122
|
+
elif hasattr(tdb_or_session, "get_bind"):
|
|
123
|
+
# Already a Session
|
|
124
|
+
session = tdb_or_session
|
|
125
|
+
else:
|
|
126
|
+
raise TradeException(
|
|
127
|
+
f"Unsupported DB object passed to exportTableToFile: {type(tdb_or_session)}"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
csvPath = csvPath or Path(tdenv.csvDir)
|
|
131
|
+
if not Path(csvPath).is_dir():
|
|
132
|
+
raise TradeException(f"Save location '{csvPath}' not found.")
|
|
133
|
+
|
|
118
134
|
uniquePfx = "unq:"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
exportPath = (Path(csvPath) / Path(tableName)).with_suffix(".csv")
|
|
136
|
+
tdenv.DEBUG0(f"Export Table '{tableName}' to '{exportPath}'")
|
|
137
|
+
|
|
138
|
+
def _fmt_ts(val):
|
|
139
|
+
if hasattr(val, "strftime"):
|
|
140
|
+
try:
|
|
141
|
+
return val.strftime("%Y-%m-%d %H:%M:%S")
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
if isinstance(val, str):
|
|
145
|
+
s = val
|
|
146
|
+
if len(s) >= 19 and s[10] == "T":
|
|
147
|
+
s = s[:10] + " " + s[11:]
|
|
148
|
+
if len(s) >= 19 and s[4] == "-" and s[7] == "-" and s[10] == " " and s[13] == ":" and s[16] == ":":
|
|
149
|
+
return s[:19]
|
|
150
|
+
return val
|
|
151
|
+
|
|
127
152
|
lineCount = 0
|
|
128
|
-
with exportPath.open("w", encoding=
|
|
129
|
-
exportOut = csv.writer(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
raise TradeException("No columns to export for table '{}'.".format(tableName))
|
|
149
|
-
|
|
150
|
-
# reverse the first two columns for some tables
|
|
151
|
-
if tableName in reverseList:
|
|
152
|
-
columnList[0], columnList[1] = columnList[1], columnList[0]
|
|
153
|
-
|
|
154
|
-
# initialize helper lists
|
|
155
|
-
csvHead = []
|
|
153
|
+
with exportPath.open("w", encoding="utf-8", newline="\n") as exportFile:
|
|
154
|
+
exportOut = csv.writer(
|
|
155
|
+
exportFile,
|
|
156
|
+
delimiter=",",
|
|
157
|
+
quotechar="'",
|
|
158
|
+
doublequote=True,
|
|
159
|
+
quoting=csv.QUOTE_NONNUMERIC,
|
|
160
|
+
lineterminator="\n",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
bind = session.get_bind()
|
|
164
|
+
inspector = inspect(bind)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
unique_cols = db_utils.get_unique_columns(session, tableName)
|
|
168
|
+
fk_list = db_utils.get_foreign_keys(session, tableName)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
raise TradeException(f"Failed to introspect table '{tableName}': {e!r}")
|
|
171
|
+
|
|
172
|
+
csvHead = []
|
|
156
173
|
stmtColumn = []
|
|
157
|
-
stmtTable
|
|
158
|
-
stmtOrder
|
|
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
|
-
# column is part of an unique index
|
|
184
|
-
csvPfx = uniquePfx + csvPfx
|
|
185
|
-
csvHead += [ "{}{}@{}.{}".format(csvPfx, keyRow['column'], keyRow['joinTable'], keyRow['joinColumn']) ]
|
|
186
|
-
stmtColumn += [ "{}.{}".format(keyRow['joinTable'], keyRow['column']) ]
|
|
187
|
-
if col['notnull']:
|
|
188
|
-
stmtTable += [ 'INNER JOIN {} {}'.format(keyRow['joinTable'], joinStmt) ]
|
|
189
|
-
else:
|
|
190
|
-
stmtTable += [ 'LEFT OUTER JOIN {} {}'.format(keyRow['joinTable'], joinStmt) ]
|
|
191
|
-
stmtOrder += [ "{}.{}".format(keyRow['joinTable'], keyRow['column']) ]
|
|
174
|
+
stmtTable = [tableName]
|
|
175
|
+
stmtOrder = []
|
|
176
|
+
is_modified_col = []
|
|
177
|
+
|
|
178
|
+
for col in inspector.get_columns(tableName):
|
|
179
|
+
col_name = col["name"]
|
|
180
|
+
fk = next((fk for fk in fk_list if fk["from"] == col_name), None)
|
|
181
|
+
if fk:
|
|
182
|
+
joinTable = fk["table"]
|
|
183
|
+
joinColumn = fk["to"]
|
|
184
|
+
join_unique_cols = db_utils.get_unique_columns(session, joinTable)
|
|
185
|
+
if not join_unique_cols:
|
|
186
|
+
raise TradeException(
|
|
187
|
+
f"No unique column found in referenced table '{joinTable}'"
|
|
188
|
+
)
|
|
189
|
+
export_col = join_unique_cols[0]
|
|
190
|
+
csvPfx = uniquePfx if col_name in unique_cols else ""
|
|
191
|
+
csvHead.append(f"{csvPfx}{col_name}@{joinTable}.{export_col}")
|
|
192
|
+
stmtColumn.append(f"{joinTable}.{export_col}")
|
|
193
|
+
is_modified_col.append(export_col == "modified")
|
|
194
|
+
nullable = bool(col.get("nullable", True))
|
|
195
|
+
join_type = "LEFT OUTER JOIN" if nullable else "INNER JOIN"
|
|
196
|
+
stmtTable.append(
|
|
197
|
+
f"{join_type} {joinTable} ON {tableName}.{col_name} = {joinTable}.{joinColumn}"
|
|
198
|
+
)
|
|
199
|
+
stmtOrder.append(f"{joinTable}.{export_col}")
|
|
192
200
|
else:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
csvHead += [ uniquePfx + col['name'] ]
|
|
197
|
-
stmtOrder += [ "{}.{}".format(tableName, col['name']) ]
|
|
201
|
+
if col_name in unique_cols:
|
|
202
|
+
csvHead.append(uniquePfx + col_name)
|
|
203
|
+
stmtOrder.append(f"{tableName}.{col_name}")
|
|
198
204
|
else:
|
|
199
|
-
csvHead
|
|
200
|
-
stmtColumn
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
sqlStmt = "SELECT {
|
|
204
|
-
if
|
|
205
|
-
sqlStmt += " ORDER BY {
|
|
206
|
-
tdenv.DEBUG1("SQL:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
for line in cur.execute(sqlStmt):
|
|
205
|
+
csvHead.append(col_name)
|
|
206
|
+
stmtColumn.append(f"{tableName}.{col_name}")
|
|
207
|
+
is_modified_col.append(col_name == "modified")
|
|
208
|
+
|
|
209
|
+
sqlStmt = f"SELECT {','.join(stmtColumn)} FROM {' '.join(stmtTable)}"
|
|
210
|
+
if stmtOrder:
|
|
211
|
+
sqlStmt += f" ORDER BY {','.join(stmtOrder)}"
|
|
212
|
+
tdenv.DEBUG1(f"SQL: {sqlStmt}")
|
|
213
|
+
|
|
214
|
+
exportFile.write(f"{','.join(csvHead)}\n")
|
|
215
|
+
|
|
216
|
+
for row in session.execute(text(sqlStmt)):
|
|
212
217
|
lineCount += 1
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
row_out = [
|
|
219
|
+
_fmt_ts(val) if is_modified_col[i] else val
|
|
220
|
+
for i, val in enumerate(row)
|
|
221
|
+
]
|
|
222
|
+
tdenv.DEBUG2(f"{lineCount}: {row_out}")
|
|
223
|
+
exportOut.writerow(row_out)
|
|
224
|
+
|
|
225
|
+
tdenv.DEBUG1(f"{lineCount} {tableName}s exported")
|
|
226
|
+
|
|
227
|
+
# Close session if we created it
|
|
228
|
+
if hasattr(tdb_or_session, "engine"):
|
|
229
|
+
session.close()
|
|
230
|
+
|
|
220
231
|
return lineCount, exportPath
|
tradedangerous/gui.py
CHANGED
|
@@ -941,9 +941,9 @@ def main(argv = None):
|
|
|
941
941
|
sys.argv = ['trade']
|
|
942
942
|
if not argv:
|
|
943
943
|
argv = sys.argv
|
|
944
|
-
if sys.hexversion <
|
|
944
|
+
if sys.hexversion < 0x30813F0:
|
|
945
945
|
raise SystemExit(
|
|
946
|
-
"Sorry: TradeDangerous requires Python 3.
|
|
946
|
+
"Sorry: TradeDangerous requires Python 3.8.19 or higher.\n"
|
|
947
947
|
"For assistance, see:\n"
|
|
948
948
|
"\tBug Tracker: https://github.com/eyeonus/Trade-Dangerous/issues\n"
|
|
949
949
|
"\tDocumentation: https://github.com/eyeonus/Trade-Dangerous/wiki\n"
|