tradedangerous 11.5.3__py3-none-any.whl → 12.0.1__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.

Files changed (47) hide show
  1. tradedangerous/cache.py +567 -395
  2. tradedangerous/cli.py +2 -2
  3. tradedangerous/commands/TEMPLATE.py +25 -26
  4. tradedangerous/commands/__init__.py +8 -16
  5. tradedangerous/commands/buildcache_cmd.py +40 -10
  6. tradedangerous/commands/buy_cmd.py +57 -46
  7. tradedangerous/commands/commandenv.py +0 -2
  8. tradedangerous/commands/export_cmd.py +78 -50
  9. tradedangerous/commands/import_cmd.py +67 -31
  10. tradedangerous/commands/market_cmd.py +52 -19
  11. tradedangerous/commands/olddata_cmd.py +120 -107
  12. tradedangerous/commands/rares_cmd.py +122 -110
  13. tradedangerous/commands/run_cmd.py +118 -66
  14. tradedangerous/commands/sell_cmd.py +52 -45
  15. tradedangerous/commands/shipvendor_cmd.py +49 -234
  16. tradedangerous/commands/station_cmd.py +55 -485
  17. tradedangerous/commands/update_cmd.py +56 -420
  18. tradedangerous/csvexport.py +173 -162
  19. tradedangerous/db/__init__.py +27 -0
  20. tradedangerous/db/adapter.py +191 -0
  21. tradedangerous/db/config.py +95 -0
  22. tradedangerous/db/engine.py +246 -0
  23. tradedangerous/db/lifecycle.py +332 -0
  24. tradedangerous/db/locks.py +208 -0
  25. tradedangerous/db/orm_models.py +455 -0
  26. tradedangerous/db/paths.py +112 -0
  27. tradedangerous/db/utils.py +661 -0
  28. tradedangerous/gui.py +2 -2
  29. tradedangerous/plugins/eddblink_plug.py +387 -251
  30. tradedangerous/plugins/spansh_plug.py +2488 -821
  31. tradedangerous/prices.py +124 -142
  32. tradedangerous/templates/TradeDangerous.sql +6 -6
  33. tradedangerous/tradecalc.py +1227 -1109
  34. tradedangerous/tradedb.py +533 -384
  35. tradedangerous/tradeenv.py +12 -1
  36. tradedangerous/version.py +1 -1
  37. {tradedangerous-11.5.3.dist-info → tradedangerous-12.0.1.dist-info}/METADATA +11 -7
  38. {tradedangerous-11.5.3.dist-info → tradedangerous-12.0.1.dist-info}/RECORD +42 -38
  39. {tradedangerous-11.5.3.dist-info → tradedangerous-12.0.1.dist-info}/WHEEL +1 -1
  40. tradedangerous/commands/update_gui.py +0 -721
  41. tradedangerous/jsonprices.py +0 -254
  42. tradedangerous/plugins/edapi_plug.py +0 -1071
  43. tradedangerous/plugins/journal_plug.py +0 -537
  44. tradedangerous/plugins/netlog_plug.py +0 -316
  45. {tradedangerous-11.5.3.dist-info → tradedangerous-12.0.1.dist-info}/entry_points.txt +0 -0
  46. {tradedangerous-11.5.3.dist-info → tradedangerous-12.0.1.dist-info/licenses}/LICENSE +0 -0
  47. {tradedangerous-11.5.3.dist-info → tradedangerous-12.0.1.dist-info}/top_level.txt +0 -0
@@ -92,39 +92,7 @@ def run(results, cmdenv, tdb):
92
92
  """
93
93
  Fetch all the data needed to display the results of a "rares"
94
94
  command. Does not actually print anything.
95
-
96
- Command execution is broken into two steps:
97
- 1. cmd.run(results, cmdenv, tdb)
98
- Gather all the data required but generate no output,
99
- 2. cmd.render(results, cmdenv, tdb)
100
- Print output to the user.
101
-
102
- This separation of concerns allows modularity; you can write
103
- a command that calls another command to fetch data for you
104
- and knowing it doesn't generate any output. Then you can
105
- process the data and return it and let the command parser
106
- decide when to turn it into output.
107
-
108
- It also opens a future door to commands that can present
109
- their data in a GUI as well as the command line by having
110
- a custom render() function.
111
-
112
- Parameters:
113
- results
114
- An object to be populated and returned
115
- cmdenv
116
- A CommandEnv object populated with the parameters
117
- for the command.
118
- tdb
119
- A TradeDB object to query against.
120
-
121
- Returns:
122
- None
123
- End execution without any output
124
- results
125
- Proceed to "render" with the output.
126
95
  """
127
-
128
96
  # Lookup the system we're currently in.
129
97
  start = cmdenv.nearSystem
130
98
  # Hoist the padSize, noPlanet and planetary parameter for convenience
@@ -134,140 +102,184 @@ def run(results, cmdenv, tdb):
134
102
  fleet = cmdenv.fleet
135
103
  odyssey = cmdenv.odyssey
136
104
  # How far we're want to cast our net.
137
- maxLy = float(cmdenv.maxLyPer or 0.)
138
-
105
+ maxLy = float(cmdenv.maxLyPer or 0.0)
106
+
139
107
  if cmdenv.illegal:
140
108
  wantIllegality = 'Y'
141
109
  elif cmdenv.legal:
142
110
  wantIllegality = 'N'
143
111
  else:
144
112
  wantIllegality = 'YN?'
145
-
113
+
146
114
  awaySystems = set()
147
115
  if cmdenv.away or cmdenv.awayFrom:
148
116
  if not cmdenv.away or not cmdenv.awayFrom:
149
- raise CommandLineError(
150
- "Invalid --away/--from usage. See --help"
151
- )
117
+ raise CommandLineError("Invalid --away/--from usage. See --help")
152
118
  minAwayDist = cmdenv.away
153
119
  for sysName in cmdenv.awayFrom:
154
120
  system = tdb.lookupPlace(sysName).system
155
121
  awaySystems.add(system)
156
-
122
+
157
123
  # Start to build up the results data.
158
124
  results.summary = ResultRow()
159
125
  results.summary.near = start
160
126
  results.summary.ly = maxLy
161
127
  results.summary.awaySystems = awaySystems
162
-
128
+
163
129
  distCheckFn = start.distanceTo
164
-
130
+
165
131
  # Look through the rares list.
166
132
  for rare in tdb.rareItemByID.values():
167
133
  if rare.illegal not in wantIllegality:
168
134
  continue
169
- if padSize: # do we care about pad size?
170
- if not rare.station.checkPadSize(padSize):
171
- continue
172
- if planetary: # do we care about planetary?
173
- if not rare.station.checkPlanetary(planetary):
174
- continue
175
- if fleet: # do we care about fleet carrier?
176
- if not rare.station.checkFleet(fleet):
177
- continue
178
- if odyssey: # do we care about Odyssey?
179
- if not rare.station.checkOdyssey(odyssey):
180
- continue
181
- if noPlanet and rare.station.planetary != 'N':
135
+ stn = rare.station
136
+ if padSize and not stn.checkPadSize(padSize):
137
+ continue
138
+ if planetary and not stn.checkPlanetary(planetary):
139
+ continue
140
+ if fleet and not stn.checkFleet(fleet):
182
141
  continue
183
- rareSys = rare.station.system
184
- # Find the un-sqrt'd distance to the system.
142
+ if odyssey and not stn.checkOdyssey(odyssey):
143
+ continue
144
+ if noPlanet and stn.planetary != 'N':
145
+ continue
146
+
147
+ rareSys = stn.system
185
148
  dist = distCheckFn(rareSys)
186
- if maxLy > 0. and dist > maxLy:
149
+ if maxLy > 0.0 and dist > maxLy:
187
150
  continue
188
-
151
+
189
152
  if awaySystems:
190
153
  awayCheck = rareSys.distanceTo
191
154
  if any(awayCheck(away) < minAwayDist for away in awaySystems):
192
155
  continue
193
-
194
- # Create a row for this item
156
+
195
157
  row = ResultRow()
196
158
  row.rare = rare
159
+ row.station = stn # <-- IMPORTANT: used by render()
197
160
  row.dist = dist
198
161
  results.rows.append(row)
199
-
162
+
200
163
  # Was anything matched?
201
- if not results:
164
+ if not results.rows:
202
165
  print("No matches found.")
203
166
  return None
204
-
167
+
168
+ # Sort safely even if rare.costCr is None (treat None as 0)
169
+ price_key = lambda row: (row.rare.costCr or 0)
170
+
205
171
  if cmdenv.sortByPrice:
206
172
  results.rows.sort(key=lambda row: row.dist)
207
- results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
173
+ results.rows.sort(key=price_key, reverse=True)
208
174
  else:
209
- results.rows.sort(key=lambda row: row.rare.costCr, reverse=True)
175
+ results.rows.sort(key=price_key, reverse=True)
210
176
  results.rows.sort(key=lambda row: row.dist)
211
-
177
+
212
178
  if cmdenv.reverse:
213
179
  results.rows.reverse()
214
-
180
+
215
181
  limit = cmdenv.limit or 0
216
182
  if limit > 0:
217
183
  results.rows = results.rows[:limit]
218
-
184
+
219
185
  return results
220
186
 
187
+
188
+
189
+
221
190
  #######################################################################
222
191
  ## Transform result set into output
223
192
 
224
193
  def render(results, cmdenv, tdb):
225
194
  """
226
- If the "run" command returned a result set and we are running
227
- from the command line, this function will be called to generate
228
- the output of the command.
195
+ Render output for 'rares' with robust None-handling.
196
+ Keeps existing column order/labels.
229
197
  """
230
-
231
- if not results.rows:
232
- raise CommandLineError("No items found.")
233
-
234
- # Calculate the longest station and rareitem name in our list.
235
- longestStnNameLen = max_len(results.rows, key=lambda row: row.rare.station.name())
236
- longestRareNameLen = max_len(results.rows, key=lambda row: row.rare.name(cmdenv.detail))
237
-
238
- # Use the formatting system to describe what our
239
- # output rows are going to look at (see formatting.py)
198
+ from ..formatting import RowFormat, max_len
199
+
200
+ rows = results.rows
201
+ if not rows:
202
+ return
203
+
204
+ # Helpers to coalesce possibly-missing attributes
205
+ def _cost(row):
206
+ try:
207
+ v = row.rare.costCr
208
+ return int(v) if v is not None else 0
209
+ except Exception:
210
+ return 0
211
+
212
+ def _rare_name(row):
213
+ try:
214
+ n = row.rare.name()
215
+ return n or "?"
216
+ except Exception:
217
+ return "?"
218
+
219
+ def _alloc(row):
220
+ val = getattr(row.rare, "allocation", None)
221
+ return str(val) if val not in (None, "") else "?"
222
+
223
+ def _rare_illegal(row):
224
+ val = getattr(row.rare, "illegal", None)
225
+ return val if val in ("Y", "N", "?") else "?"
226
+
227
+ def _stn_ls(row):
228
+ try:
229
+ v = row.station.distFromStar()
230
+ return v if v is not None else "?"
231
+ except Exception:
232
+ return "?"
233
+
234
+ def _dist(row):
235
+ try:
236
+ return float(getattr(row, "dist", 0.0) or 0.0)
237
+ except Exception:
238
+ return 0.0
239
+
240
+ def _stn_bm(row):
241
+ key = getattr(row.station, "blackMarket", "?")
242
+ return TradeDB.marketStates.get(key, key or "?")
243
+
244
+ def _pad(row):
245
+ key = getattr(row.station, "maxPadSize", "?")
246
+ return TradeDB.padSizes.get(key, key or "?")
247
+
248
+ def _plt(row):
249
+ key = getattr(row.station, "planetary", "?")
250
+ return TradeDB.planetStates.get(key, key or "?")
251
+
252
+ def _flc(row):
253
+ key = getattr(row.station, "fleet", "?")
254
+ return TradeDB.fleetStates.get(key, key or "?")
255
+
256
+ def _ody(row):
257
+ key = getattr(row.station, "odyssey", "?")
258
+ return TradeDB.odysseyStates.get(key, key or "?")
259
+
260
+ # Column widths based on safe key functions
261
+ max_stn = max_len(rows, key=lambda r: r.station.name())
262
+ max_rare = max_len(rows, key=lambda r: _rare_name(r))
263
+
240
264
  rowFmt = RowFormat()
241
- rowFmt.addColumn('Station', '<', longestStnNameLen,
242
- key=lambda row: row.rare.station.name())
243
- rowFmt.addColumn('Rare', '<', longestRareNameLen,
244
- key=lambda row: row.rare.name(cmdenv.detail))
245
- rowFmt.addColumn('Cost', '>', 10, 'n',
246
- key=lambda row: row.rare.costCr)
247
- rowFmt.addColumn('DistLy', '>', 6, '.2f',
248
- key=lambda row: row.dist)
249
- rowFmt.addColumn('Alloc', '>', 6, 'n',
250
- key=lambda row: row.rare.maxAlloc)
251
- rowFmt.addColumn('B/mkt', '>', 4,
252
- key=lambda row: TradeDB.marketStates[row.rare.illegal])
253
- rowFmt.addColumn("StnLs", '>', 10,
254
- key=lambda row: row.rare.station.distFromStar())
255
- rowFmt.addColumn('B/mkt', '>', 4,
256
- key=lambda row: TradeDB.marketStates[row.rare.station.blackMarket])
257
- rowFmt.addColumn("Pad", '>', '3',
258
- key=lambda row: TradeDB.padSizes[row.rare.station.maxPadSize])
259
- rowFmt.addColumn("Plt", '>', '3',
260
- key=lambda row: TradeDB.planetStates[row.rare.station.planetary])
261
- rowFmt.addColumn("Flc", '>', '3',
262
- key=lambda row: TradeDB.fleetStates[row.rare.station.fleet])
263
- rowFmt.addColumn("Ody", '>', '3',
264
- key=lambda row: TradeDB.odysseyStates[row.rare.station.odyssey])
265
-
266
- # Print a heading summary if the user didn't use '-q'
265
+ rowFmt.addColumn('Station', '<', max_stn, key=lambda r: r.station.name())
266
+ rowFmt.addColumn('Rare', '<', max_rare, key=lambda r: _rare_name(r))
267
+ rowFmt.addColumn('Cost', '>', 10, 'n', key=lambda r: _cost(r))
268
+ rowFmt.addColumn('DistLy', '>', 6, '.2f', key=lambda r: _dist(r))
269
+ rowFmt.addColumn('Alloc', '>', 5, key=lambda r: _alloc(r))
270
+ # First B/mkt: rare legality flag (Y/N/?)
271
+ rowFmt.addColumn('B/mkt', '>', 4, key=lambda r: _rare_illegal(r))
272
+ rowFmt.addColumn('StnLs', '>', 10, key=lambda r: _stn_ls(r))
273
+ # Second B/mkt: station black market availability via mapping
274
+ rowFmt.addColumn('B/mkt', '>', 4, key=lambda r: _stn_bm(r))
275
+ rowFmt.addColumn('Pad', '>', 3, key=lambda r: _pad(r))
276
+ rowFmt.addColumn('Plt', '>', 3, key=lambda r: _plt(r))
277
+ rowFmt.addColumn('Flc', '>', 3, key=lambda r: _flc(r))
278
+ rowFmt.addColumn('Ody', '>', 3, key=lambda r: _ody(r))
279
+
267
280
  if not cmdenv.quiet:
268
281
  heading, underline = rowFmt.heading()
269
282
  print(heading, underline, sep='\n')
270
-
271
- # Print out our results.
272
- for row in results.rows:
283
+
284
+ for row in rows:
273
285
  print(rowFmt.format(row))
@@ -11,6 +11,7 @@ from ..tradecalc import TradeCalc, Route, NoHopsError
11
11
 
12
12
  import math
13
13
  import sys
14
+ import time
14
15
 
15
16
 
16
17
  ######################################################################
@@ -385,68 +386,77 @@ def expandForJumps(tdb, cmdenv, calc, origin, jumps, srcName, purpose):
385
386
  Find all the stations you could reach if you made a given
386
387
  number of jumps away from the origin list.
387
388
  """
388
-
389
+
389
390
  assert jumps
390
-
391
+
391
392
  maxLyPer = cmdenv.emptyLyPer or cmdenv.maxLyPer
392
393
  avoidPlaces = cmdenv.avoidPlaces
393
394
  cmdenv.DEBUG0(
394
- "expanding {} reach from {} by {} jumps at {}ly per jump",
395
- srcName,
396
- origin.name(),
397
- jumps,
398
- maxLyPer,
395
+ "expanding {} reach from {} by {} jumps at {}ly per jump",
396
+ srcName,
397
+ origin.name(),
398
+ jumps,
399
+ maxLyPer,
399
400
  )
400
-
401
+
402
+ # Preserve original behavior: --to uses stationsSelling, --from uses stationsBuying
401
403
  if srcName == "--to":
402
404
  tradingList = calc.stationsSelling
403
405
  elif srcName == "--from":
404
406
  tradingList = calc.stationsBuying
405
407
  else:
406
408
  raise Exception("Unknown src")
407
-
409
+
410
+ # Ensure O(1) membership checks regardless of the underlying container type.
411
+ trading_ids = tradingList if isinstance(tradingList, set) else set(tradingList)
412
+
408
413
  stations = set()
409
414
  origins, avoid = set((origin,)), set(place for place in avoidPlaces)
410
-
415
+
411
416
  for jump in range(jumps):
412
417
  if not origins:
413
418
  break
414
- cmdenv.DEBUG1(
415
- "Ring {}: {}",
416
- jump,
417
- [sys.dbname for sys in origins]
418
- )
419
+ if getattr(cmdenv, "debug", False):
420
+ cmdenv.DEBUG1(
421
+ "Ring {}: {}",
422
+ jump,
423
+ [sys.dbname for sys in origins]
424
+ )
419
425
  thisJump, origins = origins, set()
420
426
  for system in thisJump:
421
427
  avoid.add(system)
422
428
  for stn in system.stations or ():
423
- if stn.ID not in tradingList:
424
- cmdenv.DEBUG2(
425
- "X {}/{} not in trading list",
426
- stn.system.dbname, stn.dbname,
427
- )
429
+ if stn.ID not in trading_ids:
430
+ if getattr(cmdenv, "debug", False):
431
+ cmdenv.DEBUG2(
432
+ "X {}/{} not in trading list",
433
+ stn.system.dbname, stn.dbname,
434
+ )
428
435
  continue
429
436
  if not checkStationSuitability(cmdenv, calc, stn):
437
+ if getattr(cmdenv, "debug", False):
438
+ cmdenv.DEBUG2(
439
+ "X {}/{} was not suitable",
440
+ stn.system.dbname, stn.dbname,
441
+ )
442
+ continue
443
+ if getattr(cmdenv, "debug", False):
430
444
  cmdenv.DEBUG2(
431
- "X {}/{} was not suitable",
445
+ "- {}/{} meets requirements",
432
446
  stn.system.dbname, stn.dbname,
433
447
  )
434
- continue
435
- cmdenv.DEBUG2(
436
- "- {}/{} meets requirements",
437
- stn.system.dbname, stn.dbname,
438
- )
439
448
  stations.add(stn)
440
449
  for dest, dist in tdb.genSystemsInRange(system, maxLyPer):
441
450
  if dest not in avoid:
442
451
  origins.add(dest)
443
-
444
- cmdenv.DEBUG0(
452
+
453
+ if getattr(cmdenv, "debug", False):
454
+ cmdenv.DEBUG0(
445
455
  "Expanded {} stations: {}",
446
456
  srcName,
447
457
  [stn.name() for stn in stations]
448
- )
449
-
458
+ )
459
+
450
460
  if not stations:
451
461
  if not cmdenv.emptyLyPer:
452
462
  extra = (
@@ -465,12 +475,11 @@ def expandForJumps(tdb, cmdenv, calc, origin, jumps, srcName, purpose):
465
475
  extra,
466
476
  )
467
477
  )
468
-
478
+
469
479
  stations = list(stations)
470
480
  stations.sort(key=lambda stn: stn.ID)
471
-
472
- return stations
473
481
 
482
+ return stations
474
483
 
475
484
  def checkForEmptyStationList(category, focusPlace, stationList, jumps):
476
485
  if stationList:
@@ -665,41 +674,45 @@ def checkStationSuitability(cmdenv, calc, station, src = None):
665
674
  def filterStationSet(src, cmdenv, calc, stnList):
666
675
  if not stnList:
667
676
  return stnList
668
- cmdenv.DEBUG0(
669
- "filtering {} station list: {}",
670
- src,
671
- ",".join(station.name() for station in stnList),
677
+ if getattr(cmdenv, "debug", False):
678
+ cmdenv.DEBUG0(
679
+ "filtering {} station list: {}",
680
+ src,
681
+ ",".join(station.name() for station in stnList),
672
682
  )
673
683
  stnList = tuple(
674
684
  place for place in stnList
675
685
  if isinstance(place, System) or checkStationSuitability(cmdenv, calc, place, src)
676
686
  )
677
687
  if not stnList:
688
+ # Preserve original message formatting (no behavior change)
678
689
  raise CommandLineError("No {src} station met your criteria.")
679
690
  return stnList
680
691
 
681
-
682
692
  def checkOrigins(tdb, cmdenv, calc):
693
+ # Compute eligibility once: stations must both sell and buy (same as suitability with src=None).
694
+ eligible_ids = set(calc.stationsSelling) & set(calc.stationsBuying)
695
+
683
696
  if cmdenv.origPlace:
684
697
  if cmdenv.startJumps and cmdenv.startJumps > 0:
685
698
  cmdenv.origins = expandForJumps(
686
- tdb, cmdenv, calc,
687
- cmdenv.origPlace.system,
688
- cmdenv.startJumps,
689
- "--from", "starting",
699
+ tdb, cmdenv, calc,
700
+ cmdenv.origPlace.system,
701
+ cmdenv.startJumps,
702
+ "--from", "starting",
690
703
  )
691
704
  cmdenv.origPlace = None
692
705
  elif isinstance(cmdenv.origPlace, System):
693
706
  cmdenv.DEBUG0("origPlace: System: {}", cmdenv.origPlace.name())
694
707
  if not cmdenv.origPlace.stations:
695
708
  raise CommandLineError(
696
- "No stations at --from system, {}"
697
- .format(cmdenv.origPlace.name())
698
- )
709
+ "No stations at --from system, {}"
710
+ .format(cmdenv.origPlace.name())
711
+ )
699
712
  cmdenv.origins = tuple(
700
713
  station
701
714
  for station in cmdenv.origPlace.stations
702
- if checkStationSuitability(cmdenv, calc, station)
715
+ if station.ID in eligible_ids and checkStationSuitability(cmdenv, calc, station)
703
716
  )
704
717
  else:
705
718
  cmdenv.DEBUG0("origPlace: Station: {}", cmdenv.origPlace.name())
@@ -707,8 +720,8 @@ def checkOrigins(tdb, cmdenv, calc):
707
720
  cmdenv.origins = (cmdenv.origPlace,)
708
721
  cmdenv.startStation = cmdenv.origPlace
709
722
  checkForEmptyStationList(
710
- "--from", cmdenv.origPlace,
711
- cmdenv.origins, cmdenv.startJumps
723
+ "--from", cmdenv.origPlace,
724
+ cmdenv.origins, cmdenv.startJumps
712
725
  )
713
726
  else:
714
727
  if cmdenv.startJumps:
@@ -717,21 +730,41 @@ def checkOrigins(tdb, cmdenv, calc):
717
730
  cmdenv.origins = tuple(
718
731
  station
719
732
  for station in tdb.stationByID.values()
720
- if checkStationSuitability(cmdenv, calc, station)
733
+ if station.ID in eligible_ids and checkStationSuitability(cmdenv, calc, station)
721
734
  )
722
-
735
+
723
736
  if not cmdenv.startJumps and isinstance(cmdenv.origPlace, System):
724
737
  cmdenv.origins = filterStationSet(
725
738
  '--from', cmdenv, calc, cmdenv.origins
726
739
  )
727
-
740
+
728
741
  cmdenv.origSystems = tuple(set(
729
742
  stn.system for stn in cmdenv.origins
730
743
  ))
731
744
 
732
-
733
745
  def checkDestinations(tdb, cmdenv, calc):
734
746
  cmdenv.destinations = None
747
+ showProgress = bool(getattr(cmdenv, "progress", False))
748
+ hb_interval = 0.5
749
+ last_hb = 0.0
750
+ spinner = ("|", "/", "-", "\\")
751
+ spin_i = 0
752
+
753
+ def heartbeat(seen, kept):
754
+ nonlocal last_hb, spin_i
755
+ if not showProgress:
756
+ return
757
+ now = time.time()
758
+ if (now - last_hb) < hb_interval:
759
+ return
760
+ last_hb = now
761
+ s = spinner[spin_i]
762
+ spin_i = (spin_i + 1) % len(spinner)
763
+ sys.stdout.write(
764
+ f"\r{s} Scanning stations… examined {seen:n} kept {kept:n}"
765
+ )
766
+ sys.stdout.flush()
767
+
735
768
  if cmdenv.destPlace:
736
769
  if cmdenv.endJumps and cmdenv.endJumps > 0:
737
770
  cmdenv.destinations = expandForJumps(
@@ -747,11 +780,17 @@ def checkDestinations(tdb, cmdenv, calc):
747
780
  cmdenv.destinations = (cmdenv.destPlace,)
748
781
  else:
749
782
  cmdenv.DEBUG0("destPlace: System: {}", cmdenv.destPlace.name())
750
- cmdenv.destinations = tuple(
751
- station
752
- for station in cmdenv.destPlace.stations
753
- if checkStationSuitability(cmdenv, calc, station)
754
- )
783
+ # Iterate with heartbeat instead of tuple-comprehension
784
+ dests, seen, kept = [], 0, 0
785
+ for station in cmdenv.destPlace.stations:
786
+ seen += 1
787
+ if checkStationSuitability(cmdenv, calc, station):
788
+ dests.append(station)
789
+ kept += 1
790
+ heartbeat(seen, kept)
791
+ cmdenv.destinations = tuple(dests)
792
+ if showProgress:
793
+ sys.stdout.write("\n"); sys.stdout.flush()
755
794
  checkForEmptyStationList(
756
795
  "--to", cmdenv.destPlace,
757
796
  cmdenv.destinations, cmdenv.endJumps
@@ -763,30 +802,40 @@ def checkDestinations(tdb, cmdenv, calc):
763
802
  if cmdenv.goalSystem:
764
803
  dest = tdb.lookupPlace(cmdenv.goalSystem)
765
804
  cmdenv.goalSystem = dest.system
766
-
805
+
767
806
  if cmdenv.origPlace and cmdenv.maxJumpsPer == 0:
768
807
  stationSrc = chain.from_iterable(
769
808
  system.stations for system in cmdenv.origSystems
770
809
  )
771
810
  else:
772
811
  stationSrc = tdb.stationByID.values()
773
-
774
- cmdenv.destinations = tuple(
775
- station
776
- for station in stationSrc
777
- if checkStationSuitability(cmdenv, calc, station)
778
- )
779
-
812
+
813
+ # Pre-filter by eligibility to skip obviously ineligible stations
814
+ eligible_ids = set(calc.stationsSelling) & set(calc.stationsBuying)
815
+
816
+ # Iterate with heartbeat
817
+ dests, seen, kept = [], 0, 0
818
+ for station in stationSrc:
819
+ seen += 1
820
+ if station.ID in eligible_ids and checkStationSuitability(cmdenv, calc, station):
821
+ dests.append(station)
822
+ kept += 1
823
+ heartbeat(seen, kept)
824
+ cmdenv.destinations = tuple(dests)
825
+ if showProgress:
826
+ sys.stdout.write("\n"); sys.stdout.flush()
827
+
780
828
  if not cmdenv.endJumps and isinstance(cmdenv.destPlace, System):
781
829
  cmdenv.destinations = filterStationSet(
782
830
  '--to', cmdenv, calc, cmdenv.destinations
783
831
  )
784
-
832
+
785
833
  cmdenv.destSystems = tuple(set(
786
834
  stn.system for stn in cmdenv.destinations
787
835
  ))
788
836
 
789
837
 
838
+
790
839
  def validateRunArguments(tdb, cmdenv, calc):
791
840
  """
792
841
  Process arguments to the 'run' option.
@@ -1140,6 +1189,9 @@ def run(results, cmdenv, tdb):
1140
1189
 
1141
1190
  if tdb.tradingCount == 0:
1142
1191
  raise NoDataError("Database does not contain any profitable trades.")
1192
+
1193
+ # Always show a friendly heads-up before heavy work begins.
1194
+ print("Searching for quality trades. This may take a few minutes. Please be patient.", flush=True)
1143
1195
 
1144
1196
  # Instantiate the calculator object
1145
1197
  calc = TradeCalc(tdb, cmdenv)