tradedangerous 12.0.5__py3-none-any.whl → 12.0.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.
Potentially problematic release.
This version of tradedangerous might be problematic. Click here for more details.
- tradedangerous/cache.py +135 -133
- tradedangerous/commands/buildcache_cmd.py +7 -7
- tradedangerous/commands/buy_cmd.py +4 -4
- tradedangerous/commands/export_cmd.py +11 -11
- tradedangerous/commands/import_cmd.py +12 -12
- tradedangerous/commands/market_cmd.py +17 -17
- tradedangerous/commands/olddata_cmd.py +18 -18
- tradedangerous/commands/rares_cmd.py +30 -30
- tradedangerous/commands/run_cmd.py +21 -21
- tradedangerous/commands/sell_cmd.py +5 -5
- tradedangerous/corrections.py +1 -1
- tradedangerous/csvexport.py +20 -20
- tradedangerous/db/adapter.py +9 -9
- tradedangerous/db/config.py +4 -4
- tradedangerous/db/engine.py +12 -12
- tradedangerous/db/lifecycle.py +28 -28
- tradedangerous/db/orm_models.py +42 -42
- tradedangerous/db/paths.py +3 -3
- tradedangerous/plugins/eddblink_plug.py +106 -251
- tradedangerous/plugins/spansh_plug.py +253 -253
- tradedangerous/prices.py +21 -21
- tradedangerous/tradedb.py +85 -85
- tradedangerous/tradeenv.py +2 -2
- tradedangerous/version.py +1 -1
- {tradedangerous-12.0.5.dist-info → tradedangerous-12.0.6.dist-info}/METADATA +1 -1
- {tradedangerous-12.0.5.dist-info → tradedangerous-12.0.6.dist-info}/RECORD +30 -30
- {tradedangerous-12.0.5.dist-info → tradedangerous-12.0.6.dist-info}/WHEEL +0 -0
- {tradedangerous-12.0.5.dist-info → tradedangerous-12.0.6.dist-info}/entry_points.txt +0 -0
- {tradedangerous-12.0.5.dist-info → tradedangerous-12.0.6.dist-info}/licenses/LICENSE +0 -0
- {tradedangerous-12.0.5.dist-info → tradedangerous-12.0.6.dist-info}/top_level.txt +0 -0
|
@@ -55,12 +55,12 @@ switches = [
|
|
|
55
55
|
def run(results, cmdenv, tdb: TradeDB):
|
|
56
56
|
"""
|
|
57
57
|
BRUTE-FORCE rebuild of the cache/database.
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
Semantics preserved:
|
|
60
60
|
- If DB exists and --force not given => error
|
|
61
61
|
- SQL file must exist
|
|
62
62
|
- Performs a full destructive rebuild
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
Implementation change:
|
|
65
65
|
- Delegates to tradedangerous.db.lifecycle.ensure_fresh_db with mode='force'
|
|
66
66
|
so all backend-specific checks and rebuild steps run via the central path.
|
|
@@ -68,21 +68,21 @@ def run(results, cmdenv, tdb: TradeDB):
|
|
|
68
68
|
# Deprecation note: keep short and visible but non-fatal.
|
|
69
69
|
print("NOTE: 'buildcache' is deprecated. Prefer 'update' or importer plugins. "
|
|
70
70
|
"Proceeding with a forced rebuild via db.lifecycle.ensure_fresh_db().")
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
# Honor legacy safety: require --force to overwrite an existing DB file.
|
|
73
73
|
if not cmdenv.force and tdb.dbPath.exists():
|
|
74
74
|
raise CommandLineError(
|
|
75
75
|
f"SQLite3 database '{tdb.dbFilename}' already exists.\n"
|
|
76
76
|
"Either remove the file first or use the '-f/--force' option."
|
|
77
77
|
)
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
# Ensure the SQL source exists (buildCache ultimately relies on this path).
|
|
80
80
|
if not tdb.sqlPath.exists():
|
|
81
81
|
raise CommandLineError(f"SQL File does not exist: {tdb.sqlFilename}")
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
# Force a rebuild through the lifecycle helper (works for both backends).
|
|
84
84
|
from tradedangerous.db.lifecycle import ensure_fresh_db
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
ensure_fresh_db(
|
|
87
87
|
backend=tdb.engine.dialect.name if getattr(tdb, "engine", None) else "sqlite",
|
|
88
88
|
engine=getattr(tdb, "engine", None),
|
|
@@ -93,6 +93,6 @@ def run(results, cmdenv, tdb: TradeDB):
|
|
|
93
93
|
tdenv=cmdenv,
|
|
94
94
|
rebuild=True,
|
|
95
95
|
)
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
return None
|
|
98
98
|
|
|
@@ -186,7 +186,7 @@ def sql_query(cmdenv, tdb, queries, mode):
|
|
|
186
186
|
* Item: (item_id, station_id, supply_price, supply_units)
|
|
187
187
|
"""
|
|
188
188
|
ids = list(queries.keys())
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
# Build a stable, named-parameter IN(...) list
|
|
191
191
|
params = {}
|
|
192
192
|
placeholders = []
|
|
@@ -195,7 +195,7 @@ def sql_query(cmdenv, tdb, queries, mode):
|
|
|
195
195
|
placeholders.append(f":{key}")
|
|
196
196
|
params[key] = val
|
|
197
197
|
id_list_sql = ",".join(placeholders)
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
if mode is SHIP_MODE:
|
|
200
200
|
columns = "s.ship_id, s.station_id, sh.cost, 1"
|
|
201
201
|
tables = "ShipVendor AS s JOIN Ship AS sh ON sh.ship_id = s.ship_id"
|
|
@@ -216,11 +216,11 @@ def sql_query(cmdenv, tdb, queries, mode):
|
|
|
216
216
|
if cmdenv.gt:
|
|
217
217
|
constraints.append("(s.supply_price > :gt)")
|
|
218
218
|
params["gt"] = cmdenv.gt
|
|
219
|
-
|
|
219
|
+
|
|
220
220
|
where_clause = " AND ".join(constraints)
|
|
221
221
|
stmt = f"SELECT DISTINCT {columns} FROM {tables} WHERE {where_clause}"
|
|
222
222
|
cmdenv.DEBUG0('SQL: {} ; params={}', stmt, params)
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
# Eagerly fetch to avoid closed cursor when iterating later.
|
|
225
225
|
with tdb.engine.connect() as conn:
|
|
226
226
|
result = conn.execute(text(stmt), params)
|
|
@@ -63,7 +63,7 @@ switches = [
|
|
|
63
63
|
def run(results, cmdenv, tdb):
|
|
64
64
|
"""
|
|
65
65
|
Backend-neutral export of DB tables to CSV.
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
Changes:
|
|
68
68
|
* Use tradedangerous.db.lifecycle.ensure_fresh_db(rebuild=False) to verify a usable DB
|
|
69
69
|
without rebuilding (works for SQLite and MariaDB).
|
|
@@ -86,13 +86,13 @@ def run(results, cmdenv, tdb):
|
|
|
86
86
|
f"Database is not initialized/healthy (reason: {reason}). "
|
|
87
87
|
"Use 'buildcache' or an importer to (re)build it."
|
|
88
88
|
)
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
# --- Determine export target directory (same behavior as before) ---
|
|
91
91
|
from pathlib import Path
|
|
92
92
|
exportPath = Path(cmdenv.path) if cmdenv.path else Path(tdb.dataDir)
|
|
93
93
|
if not exportPath.is_dir():
|
|
94
94
|
raise CommandLineError("Save location '{}' not found.".format(str(exportPath)))
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
# --- Announce which DB we will read from, backend-aware ---
|
|
97
97
|
try:
|
|
98
98
|
dialect = tdb.engine.dialect.name
|
|
@@ -104,17 +104,17 @@ def run(results, cmdenv, tdb):
|
|
|
104
104
|
except Exception:
|
|
105
105
|
source_label = str(getattr(tdb, "dbPath", "Unknown DB"))
|
|
106
106
|
cmdenv.NOTE("Using database {}", source_label)
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
# --- Enumerate tables using SQLAlchemy inspector (backend-neutral) ---
|
|
109
109
|
from sqlalchemy import inspect
|
|
110
110
|
inspector = inspect(tdb.engine)
|
|
111
111
|
all_tables = inspector.get_table_names() # current schema / database
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
# Optional ignore list (preserve legacy default: skip StationItem unless --all-tables)
|
|
114
114
|
ignoreList = []
|
|
115
115
|
if not getattr(cmdenv, "allTables", False):
|
|
116
116
|
ignoreList.append("StationItem")
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
# --tables filtering (case-insensitive, like old COLLATE NOCASE)
|
|
119
119
|
if getattr(cmdenv, "tables", None):
|
|
120
120
|
requested = [t.strip() for t in cmdenv.tables.split(",") if t.strip()]
|
|
@@ -129,17 +129,17 @@ def run(results, cmdenv, tdb):
|
|
|
129
129
|
table_list = sorted(set(resolved))
|
|
130
130
|
else:
|
|
131
131
|
table_list = sorted(set(all_tables))
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
# --- Export each table via csvexport (already refactored elsewhere) ---
|
|
134
134
|
for tableName in table_list:
|
|
135
135
|
if tableName in ignoreList:
|
|
136
136
|
cmdenv.NOTE("Ignore Table '{table}'", table=tableName)
|
|
137
137
|
continue
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
cmdenv.NOTE("Export Table '{table}'", table=tableName)
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
lineCount, filePath = exportTableToFile(tdb, cmdenv, tableName, exportPath)
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
# Optionally delete empty CSVs
|
|
144
144
|
if getattr(cmdenv, "deleteEmpty", False) and lineCount == 0:
|
|
145
145
|
try:
|
|
@@ -147,5 +147,5 @@ def run(results, cmdenv, tdb):
|
|
|
147
147
|
cmdenv.DEBUG0("Delete empty file {file}", file=filePath)
|
|
148
148
|
except Exception as e:
|
|
149
149
|
cmdenv.DEBUG0("Failed to delete empty file {file}: {err}", file=filePath, err=e)
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
return None
|
|
@@ -121,7 +121,7 @@ def run(results, cmdenv, tdb):
|
|
|
121
121
|
• If a plugin (-P) is specified: load it and run it (no deprecation banner).
|
|
122
122
|
• Otherwise: proceed with legacy .prices/.url flow and show a deprecation notice.
|
|
123
123
|
"""
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
# --- Plugin path (preferred; no banner) ---
|
|
126
126
|
if cmdenv.plug:
|
|
127
127
|
if cmdenv.pluginOptions:
|
|
@@ -132,16 +132,16 @@ def run(results, cmdenv, tdb):
|
|
|
132
132
|
pluginClass = plugins.load(cmdenv.plug, "ImportPlugin")
|
|
133
133
|
except plugins.PluginException as e:
|
|
134
134
|
raise CommandLineError("Plugin Error: " + str(e))
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
plugin = pluginClass(tdb, cmdenv)
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
# If plugin returns False, it fully handled the run → stop here.
|
|
139
139
|
if not plugin.run():
|
|
140
140
|
return None
|
|
141
|
-
|
|
141
|
+
|
|
142
142
|
# If plugin returns True, it’s handing control back to legacy flow below.
|
|
143
143
|
# Fall through intentionally (still no banner, as user invoked a plugin).
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
# --- Legacy .prices path (deprecated; show banner once) ---
|
|
146
146
|
# Only warn when the user is *not* using a plugin. Keep functionality intact.
|
|
147
147
|
if not cmdenv.plug:
|
|
@@ -155,23 +155,23 @@ def run(results, cmdenv, tdb):
|
|
|
155
155
|
"Solo/offline: TradeDangerous DB-Update for EDMC → https://github.com/bgol/UpdateTD\n"
|
|
156
156
|
"===================================================================\n"
|
|
157
157
|
)
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
# Refresh/close any cached handles before file ops (kept from original)
|
|
160
160
|
tdb.reloadCache()
|
|
161
161
|
tdb.close()
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
# Treat a bare http(s) string in 'filename' as a URL
|
|
164
164
|
if cmdenv.filename:
|
|
165
165
|
if re.match(r"^https?://", cmdenv.filename, re.IGNORECASE):
|
|
166
166
|
cmdenv.url, cmdenv.filename = cmdenv.filename, None
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
# Optional download step
|
|
169
169
|
if cmdenv.url:
|
|
170
170
|
cmdenv.filename = cmdenv.filename or "import.prices"
|
|
171
171
|
transfers.download(cmdenv, cmdenv.url, cmdenv.filename)
|
|
172
172
|
if cmdenv.download:
|
|
173
173
|
return None
|
|
174
|
-
|
|
174
|
+
|
|
175
175
|
# No filename? If Tk is available, prompt user (legacy behavior)
|
|
176
176
|
fh = None
|
|
177
177
|
if not cmdenv.filename and hasTkInter:
|
|
@@ -190,7 +190,7 @@ def run(results, cmdenv, tdb):
|
|
|
190
190
|
if not filename:
|
|
191
191
|
raise SystemExit("Aborted")
|
|
192
192
|
cmdenv.filename = filename
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
# Validate path or use stdin
|
|
195
195
|
if cmdenv.filename != "-":
|
|
196
196
|
filePath = Path(cmdenv.filename)
|
|
@@ -199,7 +199,7 @@ def run(results, cmdenv, tdb):
|
|
|
199
199
|
else:
|
|
200
200
|
filePath = "stdin"
|
|
201
201
|
fh = sys.stdin
|
|
202
|
-
|
|
202
|
+
|
|
203
203
|
# If a plugin was also involved and wants to finish with default flow,
|
|
204
204
|
# honour that (unchanged behavior).
|
|
205
205
|
if cmdenv.plug:
|
|
@@ -208,7 +208,7 @@ def run(results, cmdenv, tdb):
|
|
|
208
208
|
if not plugin.finish():
|
|
209
209
|
cache.regeneratePricesFile()
|
|
210
210
|
return None
|
|
211
|
-
|
|
211
|
+
|
|
212
212
|
# Legacy .prices import
|
|
213
213
|
cache.importDataFromFile(tdb, cmdenv, filePath, pricesFh=fh, reset=cmdenv.reset)
|
|
214
214
|
return None
|
|
@@ -54,24 +54,24 @@ def render_units(units, level):
|
|
|
54
54
|
def run(results, cmdenv, tdb):
|
|
55
55
|
# Lazy import to avoid any import-time tangles elsewhere.
|
|
56
56
|
from tradedangerous.db.utils import age_in_days
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
origin = cmdenv.startStation
|
|
59
59
|
if not origin.itemCount:
|
|
60
60
|
raise CommandLineError(
|
|
61
61
|
"No trade data available for {}".format(origin.name())
|
|
62
62
|
)
|
|
63
|
-
|
|
63
|
+
|
|
64
64
|
buying, selling = cmdenv.buying, cmdenv.selling
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
results.summary = ResultRow()
|
|
67
67
|
results.summary.origin = origin
|
|
68
68
|
results.summary.buying = cmdenv.buying
|
|
69
69
|
results.summary.selling = cmdenv.selling
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
# Precompute averages (unchanged)
|
|
72
72
|
tdb.getAverageSelling()
|
|
73
73
|
tdb.getAverageBuying()
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
# --- Backend-neutral query using SQLAlchemy Core + age_in_days ---
|
|
76
76
|
si = table(
|
|
77
77
|
"StationItem",
|
|
@@ -85,10 +85,10 @@ def run(results, cmdenv, tdb):
|
|
|
85
85
|
column("supply_level"),
|
|
86
86
|
column("modified"),
|
|
87
87
|
)
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
# Build session bound to current engine (needed by age_in_days)
|
|
90
90
|
session = Session(bind=tdb.engine)
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
stmt = (
|
|
93
93
|
select(
|
|
94
94
|
si.c.item_id,
|
|
@@ -98,17 +98,17 @@ def run(results, cmdenv, tdb):
|
|
|
98
98
|
)
|
|
99
99
|
.where(si.c.station_id == origin.ID)
|
|
100
100
|
)
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
rows = session.execute(stmt).fetchall()
|
|
103
103
|
session.close()
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
for r in rows:
|
|
106
106
|
it = iter(r)
|
|
107
107
|
item = tdb.itemByID[next(it)]
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
row = ResultRow()
|
|
110
110
|
row.item = item
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
row.buyCr = int(next(it) or 0)
|
|
113
113
|
row.avgBuy = tdb.avgBuying.get(item.ID, 0)
|
|
114
114
|
units, level = int(next(it) or 0), int(next(it) or 0)
|
|
@@ -119,7 +119,7 @@ def run(results, cmdenv, tdb):
|
|
|
119
119
|
hasBuy = (row.buyCr or units or level)
|
|
120
120
|
else:
|
|
121
121
|
hasBuy = False
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
row.sellCr = int(next(it) or 0)
|
|
124
124
|
row.avgSell = tdb.avgSelling.get(item.ID, 0)
|
|
125
125
|
units, level = int(next(it) or 0), int(next(it) or 0)
|
|
@@ -130,19 +130,19 @@ def run(results, cmdenv, tdb):
|
|
|
130
130
|
hasSell = (row.sellCr or units or level)
|
|
131
131
|
else:
|
|
132
132
|
hasSell = False
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
age_days = next(it)
|
|
135
135
|
row.age = float(age_days or 0.0)
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
if hasBuy or hasSell:
|
|
138
138
|
results.rows.append(row)
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
if not results.rows:
|
|
141
141
|
raise CommandLineError("No items found")
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
results.rows.sort(key=lambda row: row.item.dbname)
|
|
144
144
|
results.rows.sort(key=lambda row: row.item.category.dbname)
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
return results
|
|
147
147
|
|
|
148
148
|
#######################################################################
|
|
@@ -73,13 +73,13 @@ def run(results, cmdenv, tdb):
|
|
|
73
73
|
"""
|
|
74
74
|
from .commandenv import ResultRow
|
|
75
75
|
from tradedangerous.db.utils import age_in_days
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
cmdenv = results.cmdenv
|
|
78
78
|
tdb = cmdenv.tdb
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
results.summary = ResultRow()
|
|
81
81
|
results.limit = cmdenv.limit
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
# SQLAlchemy lightweight table defs
|
|
84
84
|
si = table(
|
|
85
85
|
"StationItem",
|
|
@@ -100,13 +100,13 @@ def run(results, cmdenv, tdb):
|
|
|
100
100
|
column("pos_y"),
|
|
101
101
|
column("pos_z"),
|
|
102
102
|
)
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
# Build session bound to current engine (age_in_days needs the session)
|
|
105
105
|
session = Session(bind=tdb.engine)
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
# Base SELECT: station_id, ls_from_star, age_days
|
|
108
108
|
age_expr = age_in_days(session, func.max(si.c.modified)).label("age_days")
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
# Optional near-system distance²
|
|
111
111
|
nearSys = cmdenv.nearSystem
|
|
112
112
|
join_sys = False
|
|
@@ -117,7 +117,7 @@ def run(results, cmdenv, tdb):
|
|
|
117
117
|
dist2_expr = (dx * dx + dy * dy + dz * dz).label("d2")
|
|
118
118
|
else:
|
|
119
119
|
dist2_expr = literal(0.0).label("d2")
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
stmt = (
|
|
122
122
|
select(
|
|
123
123
|
si.c.station_id,
|
|
@@ -133,7 +133,7 @@ def run(results, cmdenv, tdb):
|
|
|
133
133
|
.group_by(si.c.station_id, stn.c.ls_from_star, dist2_expr)
|
|
134
134
|
.order_by(age_expr.desc())
|
|
135
135
|
)
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
# Bounding box for near (keeps scan small, mirrors original)
|
|
138
138
|
if nearSys:
|
|
139
139
|
maxLy = cmdenv.maxLyPer or tdb.maxSystemLinkLy
|
|
@@ -145,15 +145,15 @@ def run(results, cmdenv, tdb):
|
|
|
145
145
|
)
|
|
146
146
|
# Radius filter: HAVING dist2 <= maxLy^2
|
|
147
147
|
stmt = stmt.having(dist2_expr <= (maxLy * maxLy))
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
# Min-age filter (apply to aggregated age of MAX(modified))
|
|
150
150
|
if cmdenv.minAge:
|
|
151
151
|
stmt = stmt.having(age_expr >= float(cmdenv.minAge))
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
# Execute and materialize rows
|
|
154
154
|
rows = session.execute(stmt).fetchall()
|
|
155
155
|
session.close()
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
# Downstream filters (unchanged)
|
|
158
158
|
padSize = cmdenv.padSize
|
|
159
159
|
planetary = cmdenv.planetary
|
|
@@ -161,7 +161,7 @@ def run(results, cmdenv, tdb):
|
|
|
161
161
|
odyssey = cmdenv.odyssey
|
|
162
162
|
noPlanet = cmdenv.noPlanet
|
|
163
163
|
mls = cmdenv.maxLs
|
|
164
|
-
|
|
164
|
+
|
|
165
165
|
for (stnID, age, ls, dist2) in rows:
|
|
166
166
|
cmdenv.DEBUG2("{}:{}:{}", stnID, age, ls)
|
|
167
167
|
row = ResultRow()
|
|
@@ -169,7 +169,7 @@ def run(results, cmdenv, tdb):
|
|
|
169
169
|
row.age = float(age or 0.0)
|
|
170
170
|
row.ls = "{:n}".format(ls) if ls else "?"
|
|
171
171
|
row.dist = (float(dist2) ** 0.5) if dist2 else 0.0
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
if padSize and not row.station.checkPadSize(padSize):
|
|
174
174
|
continue
|
|
175
175
|
if planetary and not row.station.checkPlanetary(planetary):
|
|
@@ -182,9 +182,9 @@ def run(results, cmdenv, tdb):
|
|
|
182
182
|
continue
|
|
183
183
|
if mls and row.station.lsFromStar > mls:
|
|
184
184
|
continue
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
results.rows.append(row)
|
|
187
|
-
|
|
187
|
+
|
|
188
188
|
# Route optimization and limiting (unchanged)
|
|
189
189
|
if cmdenv.route and len(results.rows) > 1:
|
|
190
190
|
def walk(start_idx, dist):
|
|
@@ -201,7 +201,7 @@ def run(results, cmdenv, tdb):
|
|
|
201
201
|
path.append(nearest)
|
|
202
202
|
dist += distFn(nearest.station.system)
|
|
203
203
|
return (path, dist)
|
|
204
|
-
|
|
204
|
+
|
|
205
205
|
if cmdenv.near:
|
|
206
206
|
bestPath = walk(0, results.rows[0].dist)
|
|
207
207
|
else:
|
|
@@ -211,10 +211,10 @@ def run(results, cmdenv, tdb):
|
|
|
211
211
|
if candidate[1] < bestPath[1]:
|
|
212
212
|
bestPath = candidate
|
|
213
213
|
results.rows[:] = bestPath[0]
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
if cmdenv.limit:
|
|
216
216
|
results.rows[:] = results.rows[:cmdenv.limit]
|
|
217
|
-
|
|
217
|
+
|
|
218
218
|
return results
|
|
219
219
|
|
|
220
220
|
|
|
@@ -103,14 +103,14 @@ def run(results, cmdenv, tdb):
|
|
|
103
103
|
odyssey = cmdenv.odyssey
|
|
104
104
|
# How far we're want to cast our net.
|
|
105
105
|
maxLy = float(cmdenv.maxLyPer or 0.0)
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
if cmdenv.illegal:
|
|
108
108
|
wantIllegality = 'Y'
|
|
109
109
|
elif cmdenv.legal:
|
|
110
110
|
wantIllegality = 'N'
|
|
111
111
|
else:
|
|
112
112
|
wantIllegality = 'YN?'
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
awaySystems = set()
|
|
115
115
|
if cmdenv.away or cmdenv.awayFrom:
|
|
116
116
|
if not cmdenv.away or not cmdenv.awayFrom:
|
|
@@ -119,15 +119,15 @@ def run(results, cmdenv, tdb):
|
|
|
119
119
|
for sysName in cmdenv.awayFrom:
|
|
120
120
|
system = tdb.lookupPlace(sysName).system
|
|
121
121
|
awaySystems.add(system)
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
# Start to build up the results data.
|
|
124
124
|
results.summary = ResultRow()
|
|
125
125
|
results.summary.near = start
|
|
126
126
|
results.summary.ly = maxLy
|
|
127
127
|
results.summary.awaySystems = awaySystems
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
distCheckFn = start.distanceTo
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
# Look through the rares list.
|
|
132
132
|
for rare in tdb.rareItemByID.values():
|
|
133
133
|
if rare.illegal not in wantIllegality:
|
|
@@ -143,45 +143,45 @@ def run(results, cmdenv, tdb):
|
|
|
143
143
|
continue
|
|
144
144
|
if noPlanet and stn.planetary != 'N':
|
|
145
145
|
continue
|
|
146
|
-
|
|
146
|
+
|
|
147
147
|
rareSys = stn.system
|
|
148
148
|
dist = distCheckFn(rareSys)
|
|
149
149
|
if maxLy > 0.0 and dist > maxLy:
|
|
150
150
|
continue
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
if awaySystems:
|
|
153
153
|
awayCheck = rareSys.distanceTo
|
|
154
154
|
if any(awayCheck(away) < minAwayDist for away in awaySystems):
|
|
155
155
|
continue
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
row = ResultRow()
|
|
158
158
|
row.rare = rare
|
|
159
159
|
row.station = stn # <-- IMPORTANT: used by render()
|
|
160
160
|
row.dist = dist
|
|
161
161
|
results.rows.append(row)
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
# Was anything matched?
|
|
164
164
|
if not results.rows:
|
|
165
165
|
print("No matches found.")
|
|
166
166
|
return None
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
# Sort safely even if rare.costCr is None (treat None as 0)
|
|
169
169
|
price_key = lambda row: (row.rare.costCr or 0)
|
|
170
|
-
|
|
170
|
+
|
|
171
171
|
if cmdenv.sortByPrice:
|
|
172
172
|
results.rows.sort(key=lambda row: row.dist)
|
|
173
173
|
results.rows.sort(key=price_key, reverse=True)
|
|
174
174
|
else:
|
|
175
175
|
results.rows.sort(key=price_key, reverse=True)
|
|
176
176
|
results.rows.sort(key=lambda row: row.dist)
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
if cmdenv.reverse:
|
|
179
179
|
results.rows.reverse()
|
|
180
|
-
|
|
180
|
+
|
|
181
181
|
limit = cmdenv.limit or 0
|
|
182
182
|
if limit > 0:
|
|
183
183
|
results.rows = results.rows[:limit]
|
|
184
|
-
|
|
184
|
+
|
|
185
185
|
return results
|
|
186
186
|
|
|
187
187
|
|
|
@@ -196,11 +196,11 @@ def render(results, cmdenv, tdb):
|
|
|
196
196
|
Keeps existing column order/labels.
|
|
197
197
|
"""
|
|
198
198
|
from ..formatting import RowFormat, max_len
|
|
199
|
-
|
|
199
|
+
|
|
200
200
|
rows = results.rows
|
|
201
201
|
if not rows:
|
|
202
202
|
return
|
|
203
|
-
|
|
203
|
+
|
|
204
204
|
# Helpers to coalesce possibly-missing attributes
|
|
205
205
|
def _cost(row):
|
|
206
206
|
try:
|
|
@@ -208,59 +208,59 @@ def render(results, cmdenv, tdb):
|
|
|
208
208
|
return int(v) if v is not None else 0
|
|
209
209
|
except Exception:
|
|
210
210
|
return 0
|
|
211
|
-
|
|
211
|
+
|
|
212
212
|
def _rare_name(row):
|
|
213
213
|
try:
|
|
214
214
|
n = row.rare.name()
|
|
215
215
|
return n or "?"
|
|
216
216
|
except Exception:
|
|
217
217
|
return "?"
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
def _alloc(row):
|
|
220
220
|
val = getattr(row.rare, "allocation", None)
|
|
221
221
|
return str(val) if val not in (None, "") else "?"
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
def _rare_illegal(row):
|
|
224
224
|
val = getattr(row.rare, "illegal", None)
|
|
225
225
|
return val if val in ("Y", "N", "?") else "?"
|
|
226
|
-
|
|
226
|
+
|
|
227
227
|
def _stn_ls(row):
|
|
228
228
|
try:
|
|
229
229
|
v = row.station.distFromStar()
|
|
230
230
|
return v if v is not None else "?"
|
|
231
231
|
except Exception:
|
|
232
232
|
return "?"
|
|
233
|
-
|
|
233
|
+
|
|
234
234
|
def _dist(row):
|
|
235
235
|
try:
|
|
236
236
|
return float(getattr(row, "dist", 0.0) or 0.0)
|
|
237
237
|
except Exception:
|
|
238
238
|
return 0.0
|
|
239
|
-
|
|
239
|
+
|
|
240
240
|
def _stn_bm(row):
|
|
241
241
|
key = getattr(row.station, "blackMarket", "?")
|
|
242
242
|
return TradeDB.marketStates.get(key, key or "?")
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
def _pad(row):
|
|
245
245
|
key = getattr(row.station, "maxPadSize", "?")
|
|
246
246
|
return TradeDB.padSizes.get(key, key or "?")
|
|
247
|
-
|
|
247
|
+
|
|
248
248
|
def _plt(row):
|
|
249
249
|
key = getattr(row.station, "planetary", "?")
|
|
250
250
|
return TradeDB.planetStates.get(key, key or "?")
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
def _flc(row):
|
|
253
253
|
key = getattr(row.station, "fleet", "?")
|
|
254
254
|
return TradeDB.fleetStates.get(key, key or "?")
|
|
255
|
-
|
|
255
|
+
|
|
256
256
|
def _ody(row):
|
|
257
257
|
key = getattr(row.station, "odyssey", "?")
|
|
258
258
|
return TradeDB.odysseyStates.get(key, key or "?")
|
|
259
|
-
|
|
259
|
+
|
|
260
260
|
# Column widths based on safe key functions
|
|
261
261
|
max_stn = max_len(rows, key=lambda r: r.station.name())
|
|
262
262
|
max_rare = max_len(rows, key=lambda r: _rare_name(r))
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
rowFmt = RowFormat()
|
|
265
265
|
rowFmt.addColumn('Station', '<', max_stn, key=lambda r: r.station.name())
|
|
266
266
|
rowFmt.addColumn('Rare', '<', max_rare, key=lambda r: _rare_name(r))
|
|
@@ -276,10 +276,10 @@ def render(results, cmdenv, tdb):
|
|
|
276
276
|
rowFmt.addColumn('Plt', '>', 3, key=lambda r: _plt(r))
|
|
277
277
|
rowFmt.addColumn('Flc', '>', 3, key=lambda r: _flc(r))
|
|
278
278
|
rowFmt.addColumn('Ody', '>', 3, key=lambda r: _ody(r))
|
|
279
|
-
|
|
279
|
+
|
|
280
280
|
if not cmdenv.quiet:
|
|
281
281
|
heading, underline = rowFmt.heading()
|
|
282
282
|
print(heading, underline, sep='\n')
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
for row in rows:
|
|
285
285
|
print(rowFmt.format(row))
|