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.
Files changed (87) hide show
  1. py.typed +1 -0
  2. trade.py +49 -0
  3. tradedangerous/__init__.py +43 -0
  4. tradedangerous/cache.py +1381 -0
  5. tradedangerous/cli.py +136 -0
  6. tradedangerous/commands/TEMPLATE.py +74 -0
  7. tradedangerous/commands/__init__.py +244 -0
  8. tradedangerous/commands/buildcache_cmd.py +102 -0
  9. tradedangerous/commands/buy_cmd.py +427 -0
  10. tradedangerous/commands/commandenv.py +372 -0
  11. tradedangerous/commands/exceptions.py +94 -0
  12. tradedangerous/commands/export_cmd.py +150 -0
  13. tradedangerous/commands/import_cmd.py +222 -0
  14. tradedangerous/commands/local_cmd.py +243 -0
  15. tradedangerous/commands/market_cmd.py +207 -0
  16. tradedangerous/commands/nav_cmd.py +252 -0
  17. tradedangerous/commands/olddata_cmd.py +270 -0
  18. tradedangerous/commands/parsing.py +221 -0
  19. tradedangerous/commands/rares_cmd.py +298 -0
  20. tradedangerous/commands/run_cmd.py +1521 -0
  21. tradedangerous/commands/sell_cmd.py +262 -0
  22. tradedangerous/commands/shipvendor_cmd.py +60 -0
  23. tradedangerous/commands/station_cmd.py +68 -0
  24. tradedangerous/commands/trade_cmd.py +181 -0
  25. tradedangerous/commands/update_cmd.py +67 -0
  26. tradedangerous/corrections.py +55 -0
  27. tradedangerous/csvexport.py +234 -0
  28. tradedangerous/db/__init__.py +27 -0
  29. tradedangerous/db/adapter.py +192 -0
  30. tradedangerous/db/config.py +107 -0
  31. tradedangerous/db/engine.py +259 -0
  32. tradedangerous/db/lifecycle.py +332 -0
  33. tradedangerous/db/locks.py +208 -0
  34. tradedangerous/db/orm_models.py +500 -0
  35. tradedangerous/db/paths.py +113 -0
  36. tradedangerous/db/utils.py +661 -0
  37. tradedangerous/edscupdate.py +565 -0
  38. tradedangerous/edsmupdate.py +474 -0
  39. tradedangerous/formatting.py +210 -0
  40. tradedangerous/fs.py +156 -0
  41. tradedangerous/gui.py +1146 -0
  42. tradedangerous/mapping.py +133 -0
  43. tradedangerous/mfd/__init__.py +103 -0
  44. tradedangerous/mfd/saitek/__init__.py +3 -0
  45. tradedangerous/mfd/saitek/directoutput.py +678 -0
  46. tradedangerous/mfd/saitek/x52pro.py +195 -0
  47. tradedangerous/misc/checkpricebounds.py +287 -0
  48. tradedangerous/misc/clipboard.py +49 -0
  49. tradedangerous/misc/coord64.py +83 -0
  50. tradedangerous/misc/csvdialect.py +57 -0
  51. tradedangerous/misc/derp-sentinel.py +35 -0
  52. tradedangerous/misc/diff-system-csvs.py +159 -0
  53. tradedangerous/misc/eddb.py +81 -0
  54. tradedangerous/misc/eddn.py +349 -0
  55. tradedangerous/misc/edsc.py +437 -0
  56. tradedangerous/misc/edsm.py +121 -0
  57. tradedangerous/misc/importeddbstats.py +54 -0
  58. tradedangerous/misc/prices-json-exp.py +179 -0
  59. tradedangerous/misc/progress.py +194 -0
  60. tradedangerous/plugins/__init__.py +249 -0
  61. tradedangerous/plugins/edcd_plug.py +371 -0
  62. tradedangerous/plugins/eddblink_plug.py +861 -0
  63. tradedangerous/plugins/edmc_batch_plug.py +133 -0
  64. tradedangerous/plugins/spansh_plug.py +2647 -0
  65. tradedangerous/prices.py +211 -0
  66. tradedangerous/submit-distances.py +422 -0
  67. tradedangerous/templates/Added.csv +37 -0
  68. tradedangerous/templates/Category.csv +17 -0
  69. tradedangerous/templates/RareItem.csv +143 -0
  70. tradedangerous/templates/TradeDangerous.sql +338 -0
  71. tradedangerous/tools.py +40 -0
  72. tradedangerous/tradecalc.py +1302 -0
  73. tradedangerous/tradedb.py +2320 -0
  74. tradedangerous/tradeenv.py +313 -0
  75. tradedangerous/tradeenv.pyi +109 -0
  76. tradedangerous/tradeexcept.py +131 -0
  77. tradedangerous/tradeorm.py +183 -0
  78. tradedangerous/transfers.py +192 -0
  79. tradedangerous/utils.py +243 -0
  80. tradedangerous/version.py +16 -0
  81. tradedangerous-12.7.6.dist-info/METADATA +106 -0
  82. tradedangerous-12.7.6.dist-info/RECORD +87 -0
  83. tradedangerous-12.7.6.dist-info/WHEEL +5 -0
  84. tradedangerous-12.7.6.dist-info/entry_points.txt +3 -0
  85. tradedangerous-12.7.6.dist-info/licenses/LICENSE +373 -0
  86. tradedangerous-12.7.6.dist-info/top_level.txt +2 -0
  87. tradegui.py +24 -0
@@ -0,0 +1,221 @@
1
+ from .exceptions import (
2
+ FleetCarrierError, OdysseyError, PadSizeError, PlanetaryError,
3
+ )
4
+
5
+ ######################################################################
6
+ # Parsing Helpers
7
+
8
+ class ParseArgument:
9
+ """
10
+ Provides argument forwarding so that 'makeSubParser' can take function-like arguments.
11
+ """
12
+ def __init__(self, *args, **kwargs):
13
+ self.args, self.kwargs = args, kwargs
14
+
15
+
16
+ class MutuallyExclusiveGroup:
17
+ def __init__(self, *args):
18
+ self.arguments = list(args)
19
+
20
+
21
+ ######################################################################
22
+ # Derived Parsers
23
+
24
+
25
+ class CreditParser(int):
26
+ """
27
+ argparse helper for parsing numeric prefixes, i.e.
28
+ 'k' for thousands, 'm' for millions and 'b' for billions.
29
+ """
30
+ suffixes = {'k': 10**3, 'm': 10**6, 'b': 10**9}
31
+
32
+ def __new__(cls, val, **kwargs):
33
+ if isinstance(val, str):
34
+ if val[-1].lower() in CreditParser.suffixes:
35
+ val = int(float(val[:-1]) * CreditParser.suffixes[val[-1].lower()])
36
+ return super().__new__(cls, val, **kwargs)
37
+
38
+
39
+ class PadSizeArgument(int):
40
+ """
41
+ argparse helper for --pad-size
42
+ """
43
+ class PadSizeParser(str):
44
+ def __new__(cls, val, **kwargs):
45
+ if not isinstance(val, str):
46
+ raise PadSizeError(val)
47
+ for v in val:
48
+ if "SML?".find(v.upper()) < 0:
49
+ raise PadSizeError(val.upper())
50
+ return super().__new__(cls, val, **kwargs)
51
+
52
+ def __init__(self):
53
+ self.args = ('--pad-size', '-p',)
54
+ self.kwargs = {
55
+ 'help': (
56
+ 'Limit to stations with one of the specified pad sizes, '
57
+ 'e.g. --pad SML? matches any pad, --pad M matches only '
58
+ 'medium pads.'
59
+ ),
60
+ 'dest': 'padSize',
61
+ 'metavar': 'PADSIZES',
62
+ 'type': 'padsize',
63
+ }
64
+
65
+
66
+ class AvoidPlacesArgument(ParseArgument):
67
+ def __init__(self):
68
+ self.args = ['--avoid']
69
+ self.kwargs = {
70
+ 'action': 'append',
71
+ 'help': (
72
+ "Don't list results for the specified systems or stations.\n"
73
+ "Names can be one-per '--avoid' or comma separated, e.g. "
74
+ "'--avoid a,b,c' or '--avoid a,b --avoid c'"
75
+ ),
76
+ }
77
+
78
+
79
+ class SwitchArgument(ParseArgument):
80
+ def __init__(self, help=None):
81
+ if isinstance(self.switches, (tuple, list)):
82
+ self.args = self.switches
83
+ else:
84
+ self.args = (self.switches,)
85
+ help = help or self.help
86
+ self.kwargs = {'action': 'store_true', 'dest': self.dest, 'help': help}
87
+
88
+
89
+ class BlackMarketSwitch(SwitchArgument):
90
+ switches = ['--black-market', '--bm']
91
+ dest = 'blackMarket'
92
+ help = 'Require stations known to have a black market.'
93
+
94
+
95
+ class ShipyardSwitch(SwitchArgument):
96
+ switches = ['--shipyard']
97
+ dest = 'shipyard'
98
+ help = 'Require stations known to have a Shipyard.'
99
+
100
+
101
+ class OutfittingSwitch(SwitchArgument):
102
+ switches = ['--outfitting']
103
+ dest = 'outfitting'
104
+ help = 'Require stations known to have Outfitting.'
105
+
106
+
107
+ class RearmSwitch(SwitchArgument):
108
+ switches = ['--rearm']
109
+ dest = 'rearm'
110
+ help = 'Require stations known to sell munitions.'
111
+
112
+
113
+ class RefuelSwitch(SwitchArgument):
114
+ switches = ['--refuel']
115
+ dest = 'refuel'
116
+ help = 'Require stations known to sell fuel.'
117
+
118
+
119
+ class RepairSwitch(SwitchArgument):
120
+ switches = ['--repair']
121
+ dest = 'repair'
122
+ help = 'Require stations known to offer repairs.'
123
+
124
+
125
+ class NoPlanetSwitch(SwitchArgument):
126
+ switches = ['--no-planet']
127
+ dest = 'noPlanet'
128
+ help = 'Require stations to be in space.'
129
+
130
+
131
+ class PlanetaryArgument(int):
132
+ """
133
+ argparse helper for --planetary
134
+ """
135
+ class PlanetaryParser(str):
136
+ def __new__(cls, val, **kwargs):
137
+ if not isinstance(val, str):
138
+ raise PlanetaryError(val)
139
+ for v in val:
140
+ if "YN?".find(v.upper()) < 0:
141
+ raise PlanetaryError(val.upper())
142
+ return super().__new__(cls, val, **kwargs)
143
+
144
+ def __init__(self):
145
+ self.args = ['--planetary']
146
+ self.kwargs = {
147
+ 'help': (
148
+ 'Limit to stations with one of the specified planetary, '
149
+ 'e.g. --pla YN? matches any station, --pla Y matches only '
150
+ 'planetary stations.'
151
+ ),
152
+ 'dest': 'planetary',
153
+ 'metavar': 'PLANETARY',
154
+ 'type': 'planetary',
155
+ }
156
+
157
+
158
+ class FleetCarrierArgument(int):
159
+ """
160
+ argparse helper for --fleet-carrier
161
+ """
162
+ class FleetCarrierParser(str):
163
+ def __new__(cls, val, **kwargs):
164
+ if not isinstance(val, str):
165
+ raise FleetCarrierError(val)
166
+ for v in val:
167
+ if "YN?".find(v.upper()) < 0:
168
+ raise FleetCarrierError(val.upper())
169
+ return super().__new__(cls, val, **kwargs)
170
+
171
+ def __init__(self):
172
+ self.args = ['--fleet-carrier', '--fc']
173
+ self.kwargs = {
174
+ 'help': (
175
+ 'Limit to stations with one of the specified fleet-carrier, '
176
+ 'e.g. --fc YN? matches any station, --fc Y matches only '
177
+ 'fleet-carrier stations.'
178
+ ),
179
+ 'dest': 'fleet',
180
+ 'metavar': 'FLEET',
181
+ 'type': 'fleet',
182
+ }
183
+
184
+ class OdysseyArgument(int):
185
+ """
186
+ argparse helper for --odyssey
187
+ """
188
+ class OdysseyParser(str):
189
+ def __new__(cls, val, **kwargs):
190
+ if not isinstance(val, str):
191
+ raise OdysseyError(val)
192
+ for v in val:
193
+ if "YN?".find(v.upper()) < 0:
194
+ raise OdysseyError(val.upper())
195
+ return super().__new__(cls, val, **kwargs)
196
+
197
+ def __init__(self):
198
+ self.args = ['--odyssey', '--od']
199
+ self.kwargs = {
200
+ 'help': (
201
+ 'Limit to stations with one of the specified odyssey, '
202
+ 'e.g. --od YN? matches any station, --od Y matches only '
203
+ 'odyssey stations.'
204
+ ),
205
+ 'dest': 'odyssey',
206
+ 'metavar': 'ODYSSEY',
207
+ 'type': 'odyssey',
208
+ }
209
+
210
+
211
+ __tdParserHelpers = {
212
+ 'credits': CreditParser,
213
+ 'padsize': PadSizeArgument.PadSizeParser,
214
+ 'planetary': PlanetaryArgument.PlanetaryParser,
215
+ 'fleet': FleetCarrierArgument.FleetCarrierParser,
216
+ 'odyssey': OdysseyArgument.OdysseyParser,
217
+ }
218
+
219
+ def registerParserHelpers(into):
220
+ for typeName, helper in __tdParserHelpers.items():
221
+ into.register('type', typeName, helper)
@@ -0,0 +1,298 @@
1
+ from .commandenv import ResultRow
2
+ from .exceptions import CommandLineError
3
+ from .parsing import (
4
+ PadSizeArgument, ParseArgument, MutuallyExclusiveGroup, NoPlanetSwitch,
5
+ PlanetaryArgument, FleetCarrierArgument, OdysseyArgument,
6
+ )
7
+ from ..formatting import RowFormat, max_len
8
+ from ..tradedb import TradeDB, select, SA_Category, SA_RareItem, SA_Station
9
+
10
+ import time
11
+
12
+
13
+ ######################################################################
14
+ # Parser config
15
+
16
+ # Displayed on the "trade.py --help" command list, etc.
17
+ help='Find rares near your current local.'
18
+ # Should match the name of the module minus the _cmd.py
19
+ name='rares'
20
+ # Displayed at the end of the "trade.py rares --help"
21
+ epilog=None
22
+ # Set to False in commands that need to operate without
23
+ # a trade database.
24
+ wantsTradeDB=True
25
+ # Required parameters
26
+ arguments = [
27
+ ParseArgument(
28
+ 'near',
29
+ help='Your current system.',
30
+ type=str,
31
+ metavar='SYSTEMNAME',
32
+ ),
33
+ ]
34
+ # Optional parameters
35
+ switches = [
36
+ ParseArgument('--ly',
37
+ help='Maximum distance to search.',
38
+ metavar='LY',
39
+ type=float,
40
+ default=180,
41
+ dest='maxLyPer',
42
+ ),
43
+ ParseArgument('--limit',
44
+ help='Maximum number of results to list.',
45
+ default=None,
46
+ type=int,
47
+ ),
48
+ PadSizeArgument(),
49
+ MutuallyExclusiveGroup(
50
+ NoPlanetSwitch(),
51
+ PlanetaryArgument(),
52
+ ),
53
+ FleetCarrierArgument(),
54
+ OdysseyArgument(),
55
+ ParseArgument('--price-sort', '-P',
56
+ help='Sort by price not distance.',
57
+ action='store_true',
58
+ default=False,
59
+ dest='sortByPrice',
60
+ ),
61
+ ParseArgument('--reverse', '-r',
62
+ help='Reverse the list.',
63
+ action='store_true',
64
+ default=False,
65
+ ),
66
+ ParseArgument('--away',
67
+ help='Require "--from" systems to be at least this far from primary system',
68
+ metavar='LY',
69
+ default=0,
70
+ type=float,
71
+ ),
72
+ MutuallyExclusiveGroup(
73
+ ParseArgument('--legal',
74
+ help='List only items known to be legal.',
75
+ action='store_true',
76
+ ),
77
+ ParseArgument('--illegal',
78
+ help='List only items known to be illegal.',
79
+ action='store_true',
80
+ )
81
+ ),
82
+ ParseArgument('--from',
83
+ help='Additional systems to range check candidates against, requires --away.',
84
+ metavar='SYSTEMNAME',
85
+ action='append',
86
+ dest='awayFrom',
87
+ ),
88
+ ]
89
+
90
+ ######################################################################
91
+ # Perform query and populate result set
92
+
93
+ def run(results, cmdenv, tdb):
94
+ """
95
+ Fetch all the data needed to display the results of a "rares"
96
+ command. Does not actually print anything.
97
+ """
98
+ # Lookup the system we're currently in.
99
+ start = cmdenv.nearSystem
100
+ # Hoist the padSize, noPlanet and planetary parameter for convenience
101
+ padSize = cmdenv.padSize
102
+ noPlanet = cmdenv.noPlanet
103
+ planetary = cmdenv.planetary
104
+ fleet = cmdenv.fleet
105
+ odyssey = cmdenv.odyssey
106
+ # How far we're want to cast our net.
107
+ maxLy = float(cmdenv.maxLyPer or 0.0)
108
+
109
+ awaySystems = set()
110
+ if cmdenv.away or cmdenv.awayFrom:
111
+ if not cmdenv.away or not cmdenv.awayFrom:
112
+ raise CommandLineError("Invalid --away/--from usage. See --help")
113
+ minAwayDist = cmdenv.away
114
+ for sysName in cmdenv.awayFrom:
115
+ system = tdb.lookupPlace(sysName).system
116
+ awaySystems.add(system)
117
+
118
+ # Start to build up the results data.
119
+ results.summary = ResultRow()
120
+ results.summary.near = start
121
+ results.summary.ly = maxLy
122
+ results.summary.awaySystems = awaySystems
123
+
124
+ distCheckFn = start.distanceTo
125
+
126
+ # Look through the rares list.
127
+ stmt = select(
128
+ SA_RareItem.rare_id,
129
+ SA_RareItem.station_id,
130
+ SA_RareItem.name,
131
+ SA_RareItem.cost,
132
+ SA_RareItem.max_allocation,
133
+ SA_RareItem.illegal,
134
+ SA_RareItem.suppressed,
135
+ SA_Category.name
136
+ ).join(SA_Category)
137
+ if cmdenv.illegal or cmdenv.legal:
138
+ stmt = stmt.where(SA_RareItem.illegal == ('Y' if cmdenv.legal else 'N'))
139
+ if noPlanet:
140
+ stmt = stmt.join(SA_Station).where(SA_Station.planetary != 'Y')
141
+
142
+ awaySystems = set()
143
+
144
+ started = time.time()
145
+ with tdb.Session() as session:
146
+ rows = session.execute(stmt).all()
147
+
148
+ for rare in rows:
149
+ stn = tdb.stationByID[rare.station_id]
150
+ if padSize and not stn.checkPadSize(padSize):
151
+ continue
152
+ if planetary and not stn.checkPlanetary(planetary):
153
+ continue
154
+ if fleet and not stn.checkFleet(fleet):
155
+ continue
156
+ if odyssey and not stn.checkOdyssey(odyssey):
157
+ continue
158
+
159
+ rareSys = stn.system
160
+ dist = distCheckFn(rareSys)
161
+ if maxLy > 0.0 and dist > maxLy:
162
+ continue
163
+
164
+ if awaySystems:
165
+ awayCheck = rareSys.distanceTo
166
+ if any(awayCheck(away) < minAwayDist for away in awaySystems):
167
+ continue
168
+
169
+ row = ResultRow()
170
+ row.rare = rare
171
+ row.station = stn # <-- IMPORTANT: used by render()
172
+ row.dist = dist
173
+ results.rows.append(row)
174
+
175
+ cmdenv.DEBUG0("Found {:n} rares in {:.3f}s", len(results.rows), time.time() - started)
176
+
177
+ # Was anything matched?
178
+ if not results.rows:
179
+ print("No matches found.")
180
+ return None
181
+
182
+ # Sort safely even if rare.costCr is None (treat None as 0)
183
+ def price_key(row):
184
+ return row.rare.cost or 0
185
+
186
+ if cmdenv.sortByPrice:
187
+ results.rows.sort(key=lambda row: row.dist)
188
+ results.rows.sort(key=price_key, reverse=True)
189
+ else:
190
+ results.rows.sort(key=price_key, reverse=True)
191
+ results.rows.sort(key=lambda row: row.dist)
192
+
193
+ if cmdenv.reverse:
194
+ results.rows.reverse()
195
+
196
+ limit = cmdenv.limit or 0
197
+ if limit > 0:
198
+ results.rows = results.rows[:limit]
199
+
200
+ return results
201
+
202
+
203
+
204
+
205
+ #######################################################################
206
+ ## Transform result set into output
207
+
208
+ def render(results, cmdenv, tdb):
209
+ """
210
+ Render output for 'rares' with robust None-handling.
211
+ Keeps existing column order/labels.
212
+ """
213
+ rows = results.rows
214
+ if not rows:
215
+ return
216
+
217
+ # Helpers to coalesce possibly-missing attributes
218
+ def _cost(row):
219
+ try:
220
+ v = row.rare.cost
221
+ return int(v) if v is not None else 0
222
+ except Exception:
223
+ return 0
224
+
225
+ def _rare_name(row):
226
+ try:
227
+ n = row.rare.name
228
+ return n or "?"
229
+ except Exception:
230
+ return "?"
231
+
232
+ def _alloc(row):
233
+ val = row.rare.max_allocation
234
+ return str(val) if val not in (None, "") else "?"
235
+
236
+ def _rare_illegal(row):
237
+ val = row.rare.illegal
238
+ return val if val in ("Y", "N", "?") else "?"
239
+
240
+ def _stn_ls(row):
241
+ try:
242
+ v = row.station.distFromStar()
243
+ return v if v is not None else "?"
244
+ except Exception:
245
+ return "?"
246
+
247
+ def _dist(row):
248
+ try:
249
+ return float(getattr(row, "dist", 0.0) or 0.0)
250
+ except Exception:
251
+ return 0.0
252
+
253
+ def _stn_bm(row):
254
+ key = getattr(row.station, "blackMarket", "?")
255
+ return TradeDB.marketStates.get(key, key or "?")
256
+
257
+ def _pad(row):
258
+ key = getattr(row.station, "maxPadSize", "?")
259
+ return TradeDB.padSizes.get(key, key or "?")
260
+
261
+ def _plt(row):
262
+ key = getattr(row.station, "planetary", "?")
263
+ return TradeDB.planetStates.get(key, key or "?")
264
+
265
+ def _flc(row):
266
+ key = getattr(row.station, "fleet", "?")
267
+ return TradeDB.fleetStates.get(key, key or "?")
268
+
269
+ def _ody(row):
270
+ key = getattr(row.station, "odyssey", "?")
271
+ return TradeDB.odysseyStates.get(key, key or "?")
272
+
273
+ # Column widths based on safe key functions
274
+ max_stn = max_len(rows, key=lambda r: r.station.name())
275
+ max_rare = max_len(rows, key=lambda r: _rare_name(r))
276
+
277
+ rowFmt = RowFormat()
278
+ rowFmt.addColumn('Station', '<', max_stn, key=lambda r: r.station.name())
279
+ rowFmt.addColumn('Rare', '<', max_rare, key=lambda r: _rare_name(r))
280
+ rowFmt.addColumn('Cost', '>', 10, 'n', key=lambda r: _cost(r))
281
+ rowFmt.addColumn('DistLy', '>', 6, '.2f', key=lambda r: _dist(r))
282
+ rowFmt.addColumn('Alloc', '>', 5, key=lambda r: _alloc(r))
283
+ # First B/mkt: rare legality flag (Y/N/?)
284
+ rowFmt.addColumn('B/mkt', '>', 4, key=lambda r: _rare_illegal(r))
285
+ rowFmt.addColumn('StnLs', '>', 10, key=lambda r: _stn_ls(r))
286
+ # Second B/mkt: station black market availability via mapping
287
+ rowFmt.addColumn('B/mkt', '>', 4, key=lambda r: _stn_bm(r))
288
+ rowFmt.addColumn('Pad', '>', 3, key=lambda r: _pad(r))
289
+ rowFmt.addColumn('Plt', '>', 3, key=lambda r: _plt(r))
290
+ rowFmt.addColumn('Flc', '>', 3, key=lambda r: _flc(r))
291
+ rowFmt.addColumn('Ody', '>', 3, key=lambda r: _ody(r))
292
+
293
+ if not cmdenv.quiet:
294
+ heading, underline = rowFmt.heading()
295
+ print(heading, underline, sep='\n')
296
+
297
+ for row in rows:
298
+ print(rowFmt.format(row))