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,222 @@
|
|
|
1
|
+
# tradedangerous/commands/import_cmd.py
|
|
2
|
+
# DEPRECATED: The legacy “.prices” import path is deprecated.
|
|
3
|
+
# Prefer supported import plugins:
|
|
4
|
+
# - trade import -P spansh (authoritative bulk dump)
|
|
5
|
+
# - trade import -P eddblink (server listings pipeline)
|
|
6
|
+
# Solo / offline players: use the TradeDangerous DB-Update plugin for EDMC:
|
|
7
|
+
# https://github.com/bgol/UpdateTD
|
|
8
|
+
#
|
|
9
|
+
# This module remains available for compatibility with old “.prices” files,
|
|
10
|
+
# but the format is being phased out and may be removed in a future release.
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from .exceptions import CommandLineError
|
|
15
|
+
from .parsing import ParseArgument, MutuallyExclusiveGroup
|
|
16
|
+
from itertools import chain
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from .. import cache, plugins, transfers
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
import typing
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
import tkinter
|
|
26
|
+
import tkinter.filedialog as tkfd
|
|
27
|
+
hasTkInter = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
hasTkInter = False
|
|
30
|
+
|
|
31
|
+
if typing.TYPE_CHECKING:
|
|
32
|
+
from ..tradedb import TradeDB
|
|
33
|
+
from ..tradeenv import TradeEnv
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
######################################################################
|
|
37
|
+
# Parser config
|
|
38
|
+
|
|
39
|
+
help = (
|
|
40
|
+
"TD data import system. On its own, this command lets you "
|
|
41
|
+
"merge station prices from a '.prices' file (entries in the "
|
|
42
|
+
"file that are older than your local data are not loaded)."
|
|
43
|
+
)
|
|
44
|
+
name = 'import'
|
|
45
|
+
epilog = (
|
|
46
|
+
"This sub-command provides a plugin infrastructure, and comes "
|
|
47
|
+
"with a module to import data from Tromador's Trading Dangerously server"
|
|
48
|
+
"(https://elite.tromador.com/).\n"
|
|
49
|
+
"See \"trade import -P eddblink -O help\" for more help."
|
|
50
|
+
)
|
|
51
|
+
wantsTradeDB = False
|
|
52
|
+
arguments = [
|
|
53
|
+
]
|
|
54
|
+
switches = [
|
|
55
|
+
MutuallyExclusiveGroup(
|
|
56
|
+
ParseArgument('filename',
|
|
57
|
+
help = (
|
|
58
|
+
"Name of the file to read, or, used with '--url', "
|
|
59
|
+
"will save the downloaded file as this name."
|
|
60
|
+
),
|
|
61
|
+
type = str,
|
|
62
|
+
default = None,
|
|
63
|
+
),
|
|
64
|
+
ParseArgument('--plug', '-P',
|
|
65
|
+
help = "Use the specified import plugin.",
|
|
66
|
+
type = str,
|
|
67
|
+
default = None,
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
ParseArgument('--url',
|
|
71
|
+
help = 'URL to download filename (default "import.prices") from.',
|
|
72
|
+
type = str,
|
|
73
|
+
default = None,
|
|
74
|
+
),
|
|
75
|
+
ParseArgument('--download',
|
|
76
|
+
help = 'Stop after downloading.',
|
|
77
|
+
action = 'store_true',
|
|
78
|
+
default = False,
|
|
79
|
+
),
|
|
80
|
+
ParseArgument(
|
|
81
|
+
'--ignore-unknown', '-i',
|
|
82
|
+
default = False, action = 'store_true',
|
|
83
|
+
dest = 'ignoreUnknown',
|
|
84
|
+
help = (
|
|
85
|
+
"Data for systems, stations and items that are not "
|
|
86
|
+
"recognized is reported as warning but skipped."
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
ParseArgument(
|
|
90
|
+
'--option', '-O',
|
|
91
|
+
default = [], action = 'append',
|
|
92
|
+
dest = 'pluginOptions',
|
|
93
|
+
help = (
|
|
94
|
+
"Provides a way to pass additional arguments to plugins."
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
MutuallyExclusiveGroup(
|
|
98
|
+
ParseArgument('--reset-all',
|
|
99
|
+
help = 'Clear the database before importing.',
|
|
100
|
+
action = 'store_true',
|
|
101
|
+
default = False,
|
|
102
|
+
),
|
|
103
|
+
ParseArgument('--merge-import', '-M',
|
|
104
|
+
help = (
|
|
105
|
+
'Merge the import file with the existing local database: '
|
|
106
|
+
'only loads values that have an explicit entry with a '
|
|
107
|
+
'newer timestamp than the existing data. Local values '
|
|
108
|
+
'are only removed if there is an explicit entry with a '
|
|
109
|
+
'0/0 demand/supply price.'
|
|
110
|
+
),
|
|
111
|
+
action = 'store_true',
|
|
112
|
+
default = False,
|
|
113
|
+
dest = 'mergeImport',
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
######################################################################
|
|
119
|
+
# Helpers
|
|
120
|
+
|
|
121
|
+
######################################################################
|
|
122
|
+
# Perform query and populate result set
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def run(results, cmdenv: TradeEnv, tdb: TradeDB):
|
|
126
|
+
"""
|
|
127
|
+
Dispatch import work:
|
|
128
|
+
• If a plugin (-P) is specified: load it and run it (no deprecation banner).
|
|
129
|
+
• Otherwise: proceed with legacy .prices/.url flow and show a deprecation notice.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
# --- Plugin path (preferred; no banner) ---
|
|
133
|
+
if cmdenv.plug:
|
|
134
|
+
if cmdenv.pluginOptions:
|
|
135
|
+
cmdenv.pluginOptions = chain.from_iterable(
|
|
136
|
+
opt.split(',') for opt in cmdenv.pluginOptions
|
|
137
|
+
)
|
|
138
|
+
try:
|
|
139
|
+
pluginClass = plugins.load(cmdenv.plug, "ImportPlugin")
|
|
140
|
+
except plugins.PluginException as e:
|
|
141
|
+
raise CommandLineError("Plugin Error: " + str(e))
|
|
142
|
+
|
|
143
|
+
plugin = pluginClass(tdb, cmdenv)
|
|
144
|
+
|
|
145
|
+
# If plugin returns False, it fully handled the run → stop here.
|
|
146
|
+
if not plugin.run():
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# If plugin returns True, it’s handing control back to legacy flow below.
|
|
150
|
+
# Fall through intentionally (still no banner, as user invoked a plugin).
|
|
151
|
+
|
|
152
|
+
# --- Legacy .prices path (deprecated; show banner once) ---
|
|
153
|
+
# Only warn when the user is *not* using a plugin. Keep functionality intact.
|
|
154
|
+
if not cmdenv.plug:
|
|
155
|
+
print(
|
|
156
|
+
"NOTE:\n"
|
|
157
|
+
"=== DEPRECATION NOTICE ============================================\n"
|
|
158
|
+
"The legacy '.prices' import is deprecated.\n"
|
|
159
|
+
"Use a supported plugin instead:\n"
|
|
160
|
+
" • trade import -P spansh\n"
|
|
161
|
+
" • trade import -P eddblink\n"
|
|
162
|
+
"Solo/offline: TradeDangerous DB-Update for EDMC → https://github.com/bgol/UpdateTD\n"
|
|
163
|
+
"===================================================================\n"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Refresh/close any cached handles before file ops (kept from original)
|
|
167
|
+
tdb.reloadCache()
|
|
168
|
+
tdb.close()
|
|
169
|
+
tdb.removePerist()
|
|
170
|
+
|
|
171
|
+
# Treat a bare http(s) string in 'filename' as a URL
|
|
172
|
+
if cmdenv.filename:
|
|
173
|
+
if re.match(r"^https?://", cmdenv.filename, re.IGNORECASE):
|
|
174
|
+
cmdenv.url, cmdenv.filename = cmdenv.filename, None
|
|
175
|
+
|
|
176
|
+
# Optional download step
|
|
177
|
+
if cmdenv.url:
|
|
178
|
+
cmdenv.filename = cmdenv.filename or "import.prices"
|
|
179
|
+
transfers.download(cmdenv, cmdenv.url, cmdenv.filename)
|
|
180
|
+
if cmdenv.download:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
# No filename? If Tk is available, prompt user (legacy behavior)
|
|
184
|
+
fh = None
|
|
185
|
+
if not cmdenv.filename and hasTkInter:
|
|
186
|
+
tk = tkinter.Tk()
|
|
187
|
+
tk.withdraw()
|
|
188
|
+
filetypes = (
|
|
189
|
+
("TradeDangerous '.prices' Files", "*.prices"),
|
|
190
|
+
("All Files", "*.*"),
|
|
191
|
+
)
|
|
192
|
+
filename = tkfd.askopenfilename(
|
|
193
|
+
title="Select the file to import",
|
|
194
|
+
initialfile="TradeDangerous.prices",
|
|
195
|
+
filetypes=filetypes,
|
|
196
|
+
initialdir='.',
|
|
197
|
+
)
|
|
198
|
+
if not filename:
|
|
199
|
+
raise SystemExit("Aborted")
|
|
200
|
+
cmdenv.filename = filename
|
|
201
|
+
|
|
202
|
+
# Validate path or use stdin
|
|
203
|
+
if cmdenv.filename != "-":
|
|
204
|
+
filePath = Path(cmdenv.filename)
|
|
205
|
+
if not filePath.is_file():
|
|
206
|
+
raise CommandLineError(f"File not found: {str(filePath)}")
|
|
207
|
+
else:
|
|
208
|
+
filePath = "stdin"
|
|
209
|
+
fh = sys.stdin
|
|
210
|
+
|
|
211
|
+
# If a plugin was also involved and wants to finish with default flow,
|
|
212
|
+
# honour that (unchanged behavior).
|
|
213
|
+
if cmdenv.plug:
|
|
214
|
+
# Plugins returning True above chose to hand control back.
|
|
215
|
+
# finish() may return False to suppress default regeneration.
|
|
216
|
+
if not plugin.finish():
|
|
217
|
+
cache.regeneratePricesFile()
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
# Legacy .prices import
|
|
221
|
+
cache.importDataFromFile(tdb, cmdenv, filePath, pricesFh=fh, reset=cmdenv.reset)
|
|
222
|
+
return False
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from itertools import chain
|
|
3
|
+
|
|
4
|
+
from .commandenv import ResultRow
|
|
5
|
+
from .exceptions import NoDataError
|
|
6
|
+
from .parsing import (
|
|
7
|
+
ParseArgument, PadSizeArgument, MutuallyExclusiveGroup, NoPlanetSwitch,
|
|
8
|
+
PlanetaryArgument, FleetCarrierArgument, OdysseyArgument, BlackMarketSwitch,
|
|
9
|
+
ShipyardSwitch, OutfittingSwitch, RearmSwitch, RefuelSwitch, RepairSwitch,
|
|
10
|
+
)
|
|
11
|
+
from tradedangerous import TradeDB
|
|
12
|
+
from tradedangerous.formatting import RowFormat, ColumnFormat, max_len
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
######################################################################
|
|
16
|
+
# Parser config
|
|
17
|
+
|
|
18
|
+
name='local'
|
|
19
|
+
help='Calculate local systems.'
|
|
20
|
+
epilog="See also the 'station' sub-command."
|
|
21
|
+
wantsTradeDB=True
|
|
22
|
+
arguments = [
|
|
23
|
+
ParseArgument(
|
|
24
|
+
'near',
|
|
25
|
+
help='Name of the system to query from.',
|
|
26
|
+
type=str,
|
|
27
|
+
metavar='SYSTEMNAME',
|
|
28
|
+
),
|
|
29
|
+
]
|
|
30
|
+
switches = [
|
|
31
|
+
ParseArgument('--ly',
|
|
32
|
+
help='Maximum light years from system.',
|
|
33
|
+
dest='ly',
|
|
34
|
+
metavar='N.NN',
|
|
35
|
+
type=float,
|
|
36
|
+
default=None,
|
|
37
|
+
),
|
|
38
|
+
PadSizeArgument(),
|
|
39
|
+
MutuallyExclusiveGroup(
|
|
40
|
+
NoPlanetSwitch(),
|
|
41
|
+
PlanetaryArgument(),
|
|
42
|
+
),
|
|
43
|
+
FleetCarrierArgument(),
|
|
44
|
+
OdysseyArgument(),
|
|
45
|
+
ParseArgument('--stations',
|
|
46
|
+
help='Limit to systems which have stations.',
|
|
47
|
+
action='store_true',
|
|
48
|
+
),
|
|
49
|
+
ParseArgument('--trading',
|
|
50
|
+
help='Limit stations to ones with price data or flagged as having '
|
|
51
|
+
'a market.',
|
|
52
|
+
action='store_true',
|
|
53
|
+
),
|
|
54
|
+
BlackMarketSwitch(),
|
|
55
|
+
ShipyardSwitch(),
|
|
56
|
+
OutfittingSwitch(),
|
|
57
|
+
RearmSwitch(),
|
|
58
|
+
RefuelSwitch(),
|
|
59
|
+
RepairSwitch(),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
######################################################################
|
|
63
|
+
# Perform query and populate result set
|
|
64
|
+
|
|
65
|
+
def run(results, cmdenv, tdb):
|
|
66
|
+
cmdenv = results.cmdenv
|
|
67
|
+
tdb = cmdenv.tdb
|
|
68
|
+
srcSystem = cmdenv.nearSystem
|
|
69
|
+
|
|
70
|
+
# Allow the user to say '0' for system-only
|
|
71
|
+
ly = cmdenv.ly if cmdenv.ly is not None else cmdenv.maxSystemLinkLy
|
|
72
|
+
|
|
73
|
+
results.summary = ResultRow()
|
|
74
|
+
results.summary.near = srcSystem
|
|
75
|
+
results.summary.ly = ly
|
|
76
|
+
results.summary.stations = 0
|
|
77
|
+
|
|
78
|
+
distances = { srcSystem: 0.0 }
|
|
79
|
+
|
|
80
|
+
# Calculate the bounding dimensions
|
|
81
|
+
for destSys, dist in tdb.genSystemsInRange(srcSystem, ly):
|
|
82
|
+
distances[destSys] = dist
|
|
83
|
+
|
|
84
|
+
showStations = cmdenv.detail
|
|
85
|
+
wantStations = cmdenv.stations
|
|
86
|
+
padSize = cmdenv.padSize
|
|
87
|
+
planetary = cmdenv.planetary
|
|
88
|
+
fleet = cmdenv.fleet
|
|
89
|
+
odyssey = cmdenv.odyssey
|
|
90
|
+
wantNoPlanet = cmdenv.noPlanet
|
|
91
|
+
wantTrading = cmdenv.trading
|
|
92
|
+
wantShipYard = cmdenv.shipyard
|
|
93
|
+
wantBlackMarket = cmdenv.blackMarket
|
|
94
|
+
wantOutfitting = cmdenv.outfitting
|
|
95
|
+
wantRearm = cmdenv.rearm
|
|
96
|
+
wantRefuel = cmdenv.refuel
|
|
97
|
+
wantRepair = cmdenv.repair
|
|
98
|
+
|
|
99
|
+
def station_filter(stations):
|
|
100
|
+
for station in stations:
|
|
101
|
+
if wantNoPlanet and station.planetary != 'N':
|
|
102
|
+
continue
|
|
103
|
+
if wantTrading and not station.isTrading:
|
|
104
|
+
continue
|
|
105
|
+
if wantBlackMarket and station.blackMarket != 'Y':
|
|
106
|
+
continue
|
|
107
|
+
if wantShipYard and station.shipyard != 'Y':
|
|
108
|
+
continue
|
|
109
|
+
if padSize and not station.checkPadSize(padSize):
|
|
110
|
+
continue
|
|
111
|
+
if planetary and not station.checkPlanetary(planetary):
|
|
112
|
+
continue
|
|
113
|
+
if fleet and not station.checkFleet(fleet):
|
|
114
|
+
continue
|
|
115
|
+
if odyssey and not station.checkOdyssey(odyssey):
|
|
116
|
+
continue
|
|
117
|
+
if wantOutfitting and station.outfitting != 'Y':
|
|
118
|
+
continue
|
|
119
|
+
if wantRearm and station.rearm != 'Y':
|
|
120
|
+
continue
|
|
121
|
+
if wantRefuel and station.refuel != 'Y':
|
|
122
|
+
continue
|
|
123
|
+
if wantRepair and station.repair != 'Y':
|
|
124
|
+
continue
|
|
125
|
+
yield station
|
|
126
|
+
|
|
127
|
+
for (system, dist) in sorted(distances.items(), key=lambda x: x[1]):
|
|
128
|
+
if showStations or wantStations:
|
|
129
|
+
stations = []
|
|
130
|
+
for (station) in station_filter(system.stations):
|
|
131
|
+
stations.append(
|
|
132
|
+
ResultRow(
|
|
133
|
+
station=station,
|
|
134
|
+
age=station.itemDataAgeStr,
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
if not stations and wantStations:
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
row = ResultRow()
|
|
141
|
+
row.system = system
|
|
142
|
+
row.dist = dist
|
|
143
|
+
row.stations = stations if showStations else []
|
|
144
|
+
results.rows.append(row)
|
|
145
|
+
results.summary.stations += len(row.stations)
|
|
146
|
+
|
|
147
|
+
return results
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def render(results, cmdenv, tdb):
|
|
151
|
+
""" render transforms a result set into output for the CLI. """
|
|
152
|
+
if not results or not results.rows:
|
|
153
|
+
distance, origin = results.summary.ly, results.summary.near.name()
|
|
154
|
+
raise NoDataError(f"No suitable systems found within {distance}ly of {origin}.")
|
|
155
|
+
|
|
156
|
+
# Compare name lengths for formatting
|
|
157
|
+
maxSysLen = max_len(results.rows, key=lambda row: row.system.name())
|
|
158
|
+
|
|
159
|
+
sysRowFmt = RowFormat().append(
|
|
160
|
+
ColumnFormat("System", '<', maxSysLen,
|
|
161
|
+
key=lambda row: row.system.name())
|
|
162
|
+
).append(
|
|
163
|
+
ColumnFormat("Dist", '>', '7', '.2f',
|
|
164
|
+
key=lambda row: row.dist)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
showStations = cmdenv.detail
|
|
168
|
+
if showStations:
|
|
169
|
+
maxStnLen = max_len(
|
|
170
|
+
chain.from_iterable(row.stations for row in results.rows),
|
|
171
|
+
key=lambda row: row.station.dbname
|
|
172
|
+
)
|
|
173
|
+
maxLsLen = max_len(
|
|
174
|
+
chain.from_iterable(row.stations for row in results.rows),
|
|
175
|
+
key=lambda row: row.station.distFromStar()
|
|
176
|
+
)
|
|
177
|
+
maxLsLen = max(maxLsLen, 5)
|
|
178
|
+
stnRowFmt = RowFormat(prefix=' / ').append(
|
|
179
|
+
ColumnFormat("Station", '.<', maxStnLen + 2,
|
|
180
|
+
key=lambda row: row.station.dbname)
|
|
181
|
+
).append(
|
|
182
|
+
ColumnFormat("StnLs", '>', maxLsLen,
|
|
183
|
+
key=lambda row: row.station.distFromStar())
|
|
184
|
+
).append(
|
|
185
|
+
ColumnFormat("Age/days", '>', 7,
|
|
186
|
+
key=lambda row: row.age)
|
|
187
|
+
).append(
|
|
188
|
+
ColumnFormat("Mkt", '>', '3',
|
|
189
|
+
key=lambda row: TradeDB.marketStates[row.station.market])
|
|
190
|
+
).append(
|
|
191
|
+
ColumnFormat("BMk", '>', '3',
|
|
192
|
+
key=lambda row: TradeDB.marketStates[row.station.blackMarket])
|
|
193
|
+
).append(
|
|
194
|
+
ColumnFormat("Shp", '>', '3',
|
|
195
|
+
key=lambda row: TradeDB.marketStates[row.station.shipyard])
|
|
196
|
+
).append(
|
|
197
|
+
ColumnFormat("Out", '>', '3',
|
|
198
|
+
key=lambda row: TradeDB.marketStates[row.station.outfitting])
|
|
199
|
+
).append(
|
|
200
|
+
ColumnFormat("Arm", '>', '3',
|
|
201
|
+
key=lambda row: TradeDB.marketStates[row.station.rearm])
|
|
202
|
+
).append(
|
|
203
|
+
ColumnFormat("Ref", '>', '3',
|
|
204
|
+
key=lambda row: TradeDB.marketStates[row.station.refuel])
|
|
205
|
+
).append(
|
|
206
|
+
ColumnFormat("Rep", '>', '3',
|
|
207
|
+
key=lambda row: TradeDB.marketStates[row.station.repair])
|
|
208
|
+
).append(
|
|
209
|
+
ColumnFormat("Pad", '>', '3',
|
|
210
|
+
key=lambda row: TradeDB.padSizes[row.station.maxPadSize])
|
|
211
|
+
).append(
|
|
212
|
+
ColumnFormat("Plt", '>', '3',
|
|
213
|
+
key=lambda row: TradeDB.planetStates[row.station.planetary])
|
|
214
|
+
).append(
|
|
215
|
+
ColumnFormat("Flc", '>', '3',
|
|
216
|
+
key=lambda row: TradeDB.fleetStates[row.station.fleet])
|
|
217
|
+
).append(
|
|
218
|
+
ColumnFormat("Ody", '>', '3',
|
|
219
|
+
key=lambda row: TradeDB.odysseyStates[row.station.odyssey])
|
|
220
|
+
)
|
|
221
|
+
if cmdenv.detail > 1:
|
|
222
|
+
stnRowFmt.append(
|
|
223
|
+
ColumnFormat("Itms", ">", 4,
|
|
224
|
+
key=lambda row: row.station.itemCount)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
cmdenv.DEBUG0(
|
|
228
|
+
"Systems within {ly:<5.2f}ly of {sys}.\n",
|
|
229
|
+
sys=results.summary.near.name(),
|
|
230
|
+
ly=results.summary.ly,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if not cmdenv.quiet:
|
|
234
|
+
heading, underline = sysRowFmt.heading()
|
|
235
|
+
if showStations:
|
|
236
|
+
print(heading)
|
|
237
|
+
heading, underline = stnRowFmt.heading()
|
|
238
|
+
print(heading, underline, sep='\n')
|
|
239
|
+
|
|
240
|
+
for row in results.rows:
|
|
241
|
+
print(sysRowFmt.format(row))
|
|
242
|
+
for stnRow in row.stations:
|
|
243
|
+
print(stnRowFmt.format(stnRow))
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from .commandenv import ResultRow
|
|
2
|
+
from .exceptions import CommandLineError
|
|
3
|
+
from .parsing import (
|
|
4
|
+
ParseArgument, MutuallyExclusiveGroup,
|
|
5
|
+
)
|
|
6
|
+
from ..formatting import RowFormat
|
|
7
|
+
from sqlalchemy import select, table, column
|
|
8
|
+
from sqlalchemy.orm import Session
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
######################################################################
|
|
12
|
+
# Parser config
|
|
13
|
+
|
|
14
|
+
help='Lists items bought/sold at a given station.'
|
|
15
|
+
name='market'
|
|
16
|
+
epilog=None
|
|
17
|
+
wantsTradeDB=True
|
|
18
|
+
arguments = [
|
|
19
|
+
ParseArgument(
|
|
20
|
+
'origin',
|
|
21
|
+
help='Station being queried.',
|
|
22
|
+
metavar='STATIONNAME',
|
|
23
|
+
type=str,
|
|
24
|
+
),
|
|
25
|
+
]
|
|
26
|
+
switches = [
|
|
27
|
+
MutuallyExclusiveGroup(
|
|
28
|
+
ParseArgument(
|
|
29
|
+
'--buying', '-B',
|
|
30
|
+
help='Show items station is buying',
|
|
31
|
+
action='store_true',
|
|
32
|
+
),
|
|
33
|
+
ParseArgument(
|
|
34
|
+
'--selling', '-S',
|
|
35
|
+
help='Show items station is selling',
|
|
36
|
+
action='store_true',
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
######################################################################
|
|
42
|
+
# Perform query and populate result set
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def render_units(units, level):
|
|
46
|
+
if level == 0:
|
|
47
|
+
return '-'
|
|
48
|
+
if units < 0:
|
|
49
|
+
return '?'
|
|
50
|
+
levelNames = { -1: '?', 1: 'L', 2: 'M', 3: 'H' }
|
|
51
|
+
return "{:n}{}".format(units, levelNames[level])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run(results, cmdenv, tdb):
|
|
55
|
+
# Lazy import to avoid any import-time tangles elsewhere.
|
|
56
|
+
from tradedangerous.db.utils import age_in_days
|
|
57
|
+
|
|
58
|
+
origin = cmdenv.startStation
|
|
59
|
+
if not origin.itemCount:
|
|
60
|
+
raise CommandLineError(
|
|
61
|
+
"No trade data available for {}".format(origin.name())
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
buying, selling = cmdenv.buying, cmdenv.selling
|
|
65
|
+
|
|
66
|
+
results.summary = ResultRow()
|
|
67
|
+
results.summary.origin = origin
|
|
68
|
+
results.summary.buying = cmdenv.buying
|
|
69
|
+
results.summary.selling = cmdenv.selling
|
|
70
|
+
|
|
71
|
+
# Precompute averages (unchanged)
|
|
72
|
+
tdb.getAverageSelling()
|
|
73
|
+
tdb.getAverageBuying()
|
|
74
|
+
|
|
75
|
+
# --- Backend-neutral query using SQLAlchemy Core + age_in_days ---
|
|
76
|
+
si = table(
|
|
77
|
+
"StationItem",
|
|
78
|
+
column("item_id"),
|
|
79
|
+
column("station_id"),
|
|
80
|
+
column("demand_price"),
|
|
81
|
+
column("demand_units"),
|
|
82
|
+
column("demand_level"),
|
|
83
|
+
column("supply_price"),
|
|
84
|
+
column("supply_units"),
|
|
85
|
+
column("supply_level"),
|
|
86
|
+
column("modified"),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Build session bound to current engine (needed by age_in_days)
|
|
90
|
+
session = Session(bind=tdb.engine)
|
|
91
|
+
|
|
92
|
+
stmt = (
|
|
93
|
+
select(
|
|
94
|
+
si.c.item_id,
|
|
95
|
+
si.c.demand_price, si.c.demand_units, si.c.demand_level,
|
|
96
|
+
si.c.supply_price, si.c.supply_units, si.c.supply_level,
|
|
97
|
+
age_in_days(session, si.c.modified).label("age_days"),
|
|
98
|
+
)
|
|
99
|
+
.where(si.c.station_id == origin.ID)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
rows = session.execute(stmt).fetchall()
|
|
103
|
+
session.close()
|
|
104
|
+
|
|
105
|
+
for r in rows:
|
|
106
|
+
it = iter(r)
|
|
107
|
+
item = tdb.itemByID[next(it)]
|
|
108
|
+
|
|
109
|
+
row = ResultRow()
|
|
110
|
+
row.item = item
|
|
111
|
+
|
|
112
|
+
row.buyCr = int(next(it) or 0)
|
|
113
|
+
row.avgBuy = tdb.avgBuying.get(item.ID, 0)
|
|
114
|
+
units, level = int(next(it) or 0), int(next(it) or 0)
|
|
115
|
+
row.buyUnits = units
|
|
116
|
+
row.buyLevel = level
|
|
117
|
+
row.demand = render_units(units, level)
|
|
118
|
+
if not selling:
|
|
119
|
+
hasBuy = (row.buyCr or units or level)
|
|
120
|
+
else:
|
|
121
|
+
hasBuy = False
|
|
122
|
+
|
|
123
|
+
row.sellCr = int(next(it) or 0)
|
|
124
|
+
row.avgSell = tdb.avgSelling.get(item.ID, 0)
|
|
125
|
+
units, level = int(next(it) or 0), int(next(it) or 0)
|
|
126
|
+
row.sellUnits = units
|
|
127
|
+
row.sellLevel = level
|
|
128
|
+
row.supply = render_units(units, level)
|
|
129
|
+
if not buying:
|
|
130
|
+
hasSell = (row.sellCr or units or level)
|
|
131
|
+
else:
|
|
132
|
+
hasSell = False
|
|
133
|
+
|
|
134
|
+
age_days = next(it)
|
|
135
|
+
row.age = float(age_days or 0.0)
|
|
136
|
+
|
|
137
|
+
if hasBuy or hasSell:
|
|
138
|
+
results.rows.append(row)
|
|
139
|
+
|
|
140
|
+
if not results.rows:
|
|
141
|
+
raise CommandLineError("No items found")
|
|
142
|
+
|
|
143
|
+
results.rows.sort(key=lambda row: row.item.dbname)
|
|
144
|
+
results.rows.sort(key=lambda row: row.item.category.dbname)
|
|
145
|
+
|
|
146
|
+
return results
|
|
147
|
+
|
|
148
|
+
#######################################################################
|
|
149
|
+
## Transform result set into output
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def render(results, cmdenv, tdb):
|
|
153
|
+
longest = max(results.rows, key=lambda row: len(row.item.name()))
|
|
154
|
+
longestLen = len(longest.item.name())
|
|
155
|
+
longestDmd = max(results.rows, key=lambda row: len(row.demand)).demand
|
|
156
|
+
longestSup = max(results.rows, key=lambda row: len(row.supply)).supply
|
|
157
|
+
dmdLen = max(len(longestDmd), len("Demand"))
|
|
158
|
+
supLen = max(len(longestSup), len("Supply"))
|
|
159
|
+
|
|
160
|
+
showCategories = (cmdenv.detail > 0)
|
|
161
|
+
|
|
162
|
+
rowFmt = RowFormat()
|
|
163
|
+
if showCategories:
|
|
164
|
+
rowFmt.prefix = ' '
|
|
165
|
+
|
|
166
|
+
sellPred = lambda row: row.sellCr != 0 and row.supply != '-' # noqa: E731
|
|
167
|
+
buyPred = lambda row: row.buyCr != 0 and row.demand != '-' # noqa: E731
|
|
168
|
+
|
|
169
|
+
rowFmt.addColumn('Item', '<', longestLen,
|
|
170
|
+
key=lambda row: row.item.name())
|
|
171
|
+
if not cmdenv.selling:
|
|
172
|
+
rowFmt.addColumn('Buying', '>', 7, 'n',
|
|
173
|
+
key=lambda row: row.buyCr,
|
|
174
|
+
pred=buyPred)
|
|
175
|
+
if cmdenv.detail:
|
|
176
|
+
rowFmt.addColumn('Avg', '>', 7, 'n',
|
|
177
|
+
key=lambda row: row.avgBuy,
|
|
178
|
+
pred=buyPred)
|
|
179
|
+
if cmdenv.detail > 1:
|
|
180
|
+
rowFmt.addColumn('Demand', '>', dmdLen,
|
|
181
|
+
key=lambda row: row.demand,
|
|
182
|
+
pred=buyPred)
|
|
183
|
+
if not cmdenv.buying:
|
|
184
|
+
rowFmt.addColumn('Selling', '>', 7, 'n',
|
|
185
|
+
key=lambda row: row.sellCr,
|
|
186
|
+
pred=sellPred)
|
|
187
|
+
if cmdenv.detail:
|
|
188
|
+
rowFmt.addColumn('Avg', '>', 7, 'n',
|
|
189
|
+
key=lambda row: row.avgSell,
|
|
190
|
+
pred=sellPred)
|
|
191
|
+
rowFmt.addColumn('Supply', '>', supLen,
|
|
192
|
+
key=lambda row: row.supply,
|
|
193
|
+
pred=sellPred)
|
|
194
|
+
if cmdenv.detail:
|
|
195
|
+
rowFmt.addColumn('Age/Days', '>', 7, '.2f',
|
|
196
|
+
key=lambda row: row.age)
|
|
197
|
+
|
|
198
|
+
if not cmdenv.quiet:
|
|
199
|
+
heading, underline = rowFmt.heading()
|
|
200
|
+
print(heading, underline, sep='\n')
|
|
201
|
+
|
|
202
|
+
lastCat = None
|
|
203
|
+
for row in results.rows:
|
|
204
|
+
if showCategories and row.item.category is not lastCat:
|
|
205
|
+
print("+{}".format(row.item.category.name()))
|
|
206
|
+
lastCat = row.item.category
|
|
207
|
+
print(rowFmt.format(row))
|