tradedangerous 10.16.17__py3-none-any.whl → 11.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tradedangerous might be problematic. Click here for more details.

Files changed (74) hide show
  1. tradedangerous/__init__.py +4 -4
  2. tradedangerous/cache.py +183 -148
  3. tradedangerous/cli.py +2 -7
  4. tradedangerous/commands/TEMPLATE.py +1 -2
  5. tradedangerous/commands/__init__.py +2 -4
  6. tradedangerous/commands/buildcache_cmd.py +6 -11
  7. tradedangerous/commands/buy_cmd.py +11 -12
  8. tradedangerous/commands/commandenv.py +16 -15
  9. tradedangerous/commands/exceptions.py +6 -4
  10. tradedangerous/commands/export_cmd.py +2 -4
  11. tradedangerous/commands/import_cmd.py +3 -5
  12. tradedangerous/commands/local_cmd.py +16 -25
  13. tradedangerous/commands/market_cmd.py +9 -8
  14. tradedangerous/commands/nav_cmd.py +17 -25
  15. tradedangerous/commands/olddata_cmd.py +9 -15
  16. tradedangerous/commands/parsing.py +9 -6
  17. tradedangerous/commands/rares_cmd.py +9 -10
  18. tradedangerous/commands/run_cmd.py +25 -26
  19. tradedangerous/commands/sell_cmd.py +9 -9
  20. tradedangerous/commands/shipvendor_cmd.py +4 -7
  21. tradedangerous/commands/station_cmd.py +8 -14
  22. tradedangerous/commands/trade_cmd.py +5 -10
  23. tradedangerous/commands/update_cmd.py +10 -7
  24. tradedangerous/commands/update_gui.py +1 -3
  25. tradedangerous/corrections.py +1 -3
  26. tradedangerous/csvexport.py +8 -8
  27. tradedangerous/edscupdate.py +4 -6
  28. tradedangerous/edsmupdate.py +4 -4
  29. tradedangerous/formatting.py +53 -40
  30. tradedangerous/fs.py +6 -6
  31. tradedangerous/gui.py +53 -62
  32. tradedangerous/jsonprices.py +8 -16
  33. tradedangerous/mapping.py +4 -3
  34. tradedangerous/mfd/__init__.py +2 -4
  35. tradedangerous/mfd/saitek/__init__.py +0 -1
  36. tradedangerous/mfd/saitek/directoutput.py +8 -11
  37. tradedangerous/mfd/saitek/x52pro.py +5 -7
  38. tradedangerous/misc/checkpricebounds.py +2 -3
  39. tradedangerous/misc/clipboard.py +2 -3
  40. tradedangerous/misc/coord64.py +2 -1
  41. tradedangerous/misc/derp-sentinel.py +1 -1
  42. tradedangerous/misc/diff-system-csvs.py +3 -0
  43. tradedangerous/misc/eddb.py +1 -3
  44. tradedangerous/misc/eddn.py +2 -2
  45. tradedangerous/misc/edsc.py +7 -14
  46. tradedangerous/misc/edsm.py +1 -8
  47. tradedangerous/misc/importeddbstats.py +2 -1
  48. tradedangerous/misc/prices-json-exp.py +7 -5
  49. tradedangerous/misc/progress.py +2 -2
  50. tradedangerous/plugins/__init__.py +2 -2
  51. tradedangerous/plugins/edapi_plug.py +13 -19
  52. tradedangerous/plugins/edcd_plug.py +4 -5
  53. tradedangerous/plugins/eddblink_plug.py +14 -17
  54. tradedangerous/plugins/edmc_batch_plug.py +3 -5
  55. tradedangerous/plugins/journal_plug.py +2 -1
  56. tradedangerous/plugins/netlog_plug.py +5 -5
  57. tradedangerous/plugins/spansh_plug.py +393 -176
  58. tradedangerous/prices.py +19 -20
  59. tradedangerous/submit-distances.py +3 -8
  60. tradedangerous/templates/TradeDangerous.sql +305 -306
  61. tradedangerous/trade.py +12 -5
  62. tradedangerous/tradecalc.py +30 -34
  63. tradedangerous/tradedb.py +140 -206
  64. tradedangerous/tradeenv.py +143 -69
  65. tradedangerous/tradegui.py +4 -2
  66. tradedangerous/transfers.py +23 -20
  67. tradedangerous/version.py +1 -1
  68. {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/METADATA +2 -2
  69. tradedangerous-11.0.0.dist-info/RECORD +79 -0
  70. tradedangerous-10.16.17.dist-info/RECORD +0 -79
  71. {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/LICENSE +0 -0
  72. {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/WHEEL +0 -0
  73. {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/entry_points.txt +0 -0
  74. {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/top_level.txt +0 -0
tradedangerous/cache.py CHANGED
@@ -20,18 +20,27 @@
20
20
  # TODO: Split prices into per-system or per-station files so that
21
21
  # we can tell how old data for a specific system is.
22
22
 
23
- from collections import namedtuple
24
- from pathlib import Path
25
- from .tradeexcept import TradeException
23
+ from __future__ import annotations
26
24
 
27
- from . import corrections, utils
25
+ from pathlib import Path
28
26
  import csv
29
- import math
30
27
  import os
31
- from . import prices
32
28
  import re
33
29
  import sqlite3
34
30
  import sys
31
+ import typing
32
+
33
+ from .tradeexcept import TradeException
34
+ from . import corrections, utils
35
+ from . import prices
36
+
37
+
38
+ # For mypy/pylint type checking
39
+ if typing.TYPE_CHECKING:
40
+ from typing import Any, Optional, TextIO
41
+
42
+ from .tradeenv import TradeEnv
43
+
35
44
 
36
45
  ######################################################################
37
46
  # Regular expression patterns. Here be draegons.
@@ -76,24 +85,23 @@ qtyLevelFrag = r"""
76
85
  """
77
86
  newItemPriceRe = re.compile(r"""
78
87
  ^
79
- {base_f}
88
+ {itemPriceFrag}
80
89
  (
81
90
  \s+
82
91
  # demand units and level
83
- (?P<demand> {qtylvl_f})
92
+ (?P<demand> {qtyLevelFrag})
84
93
  \s+
85
94
  # supply units and level
86
- (?P<supply> {qtylvl_f})
95
+ (?P<supply> {qtyLevelFrag})
87
96
  # time is optional
88
97
  (?:
89
98
  \s+
90
- {time_f}
99
+ {timeFrag}
91
100
  )?
92
101
  )?
93
102
  \s*
94
103
  $
95
- """.format(base_f = itemPriceFrag, qtylvl_f = qtyLevelFrag, time_f = timeFrag),
96
- re.IGNORECASE + re.VERBOSE)
104
+ """, re.IGNORECASE + re.VERBOSE)
97
105
 
98
106
  ######################################################################
99
107
  # Exception classes
@@ -108,18 +116,14 @@ class BuildCacheBaseException(TradeException):
108
116
  error Description of the error
109
117
  """
110
118
 
111
- def __init__(self, fromFile, lineNo, error = None):
119
+ def __init__(self, fromFile: Path, lineNo: int, error: str = None) -> None:
112
120
  self.fileName = fromFile.name
113
121
  self.lineNo = lineNo
114
122
  self.category = "ERROR"
115
123
  self.error = error or "UNKNOWN ERROR"
116
124
 
117
- def __str__(self):
118
- return "{}:{} {} {}".format(
119
- self.fileName, self.lineNo,
120
- self.category,
121
- self.error,
122
- )
125
+ def __str__(self) -> str:
126
+ return f'{self.fileName}:{self.lineNo} {self.category} {self.error}'
123
127
 
124
128
 
125
129
  class UnknownSystemError(BuildCacheBaseException):
@@ -127,9 +131,8 @@ class UnknownSystemError(BuildCacheBaseException):
127
131
  Raised when the file contains an unknown star name.
128
132
  """
129
133
 
130
- def __init__(self, fromFile, lineNo, key):
131
- error = 'Unrecognized SYSTEM: "{}"'.format(key)
132
- super().__init__(fromFile, lineNo, error)
134
+ def __init__(self, fromFile: Path, lineNo: int, key: str) -> None:
135
+ super().__init__(fromFile, lineNo, f'Unrecognized SYSTEM: "{key}"')
133
136
 
134
137
 
135
138
  class UnknownStationError(BuildCacheBaseException):
@@ -137,9 +140,8 @@ class UnknownStationError(BuildCacheBaseException):
137
140
  Raised when the file contains an unknown star/station name.
138
141
  """
139
142
 
140
- def __init__(self, fromFile, lineNo, key):
141
- error = 'Unrecognized STAR/Station: "{}"'.format(key)
142
- super().__init__(fromFile, lineNo, error)
143
+ def __init__(self, fromFile: Path, lineNo: int, key: str) -> None:
144
+ super().__init__(fromFile, lineNo, f'Unrecognized STAR/Station: "{key}"')
143
145
 
144
146
 
145
147
  class UnknownItemError(BuildCacheBaseException):
@@ -149,9 +151,8 @@ class UnknownItemError(BuildCacheBaseException):
149
151
  itemName Key we tried to look up.
150
152
  """
151
153
 
152
- def __init__(self, fromFile, lineNo, itemName):
153
- error = 'Unrecognized item name: "{}"'.format(itemName)
154
- super().__init__(fromFile, lineNo, error)
154
+ def __init__(self, fromFile: Path, lineNo: int, itemName: str) -> None:
155
+ super().__init__(fromFile, lineNo, f'Unrecognized item name: "{itemName}"')
155
156
 
156
157
 
157
158
  class DuplicateKeyError(BuildCacheBaseException):
@@ -159,14 +160,9 @@ class DuplicateKeyError(BuildCacheBaseException):
159
160
  Raised when an item is being redefined.
160
161
  """
161
162
 
162
- def __init__(self, fromFile, lineNo, keyType, keyValue, prevLineNo):
163
+ def __init__(self, fromFile: Path, lineNo: int, keyType: str, keyValue: str, prevLineNo: int) -> None:
163
164
  super().__init__(fromFile, lineNo,
164
- "Second occurrance of {keytype} \"{keyval}\", "
165
- "previous entry at line {prev}.".format(
166
- keytype = keyType,
167
- keyval = keyValue,
168
- prev = prevLineNo
169
- ))
165
+ f'Second occurrance of {keyType} "{keyValue}", previous entry at line {prevLineNo}.')
170
166
 
171
167
 
172
168
  class DeletedKeyError(BuildCacheBaseException):
@@ -175,11 +171,11 @@ class DeletedKeyError(BuildCacheBaseException):
175
171
  corrections file.
176
172
  """
177
173
 
178
- def __init__(self, fromFile, lineNo, keyType, keyValue):
179
- super().__init__(fromFile, lineNo,
180
- "{} '{}' is marked as DELETED and should not be used.".format(
181
- keyType, keyValue
182
- ))
174
+ def __init__(self, fromFile: Path, lineNo: int, keyType: str, keyValue: str) -> None:
175
+ super().__init__(
176
+ fromFile, lineNo,
177
+ f'{keyType} "{keyValue}" is marked as DELETED and should not be used.'
178
+ )
183
179
 
184
180
 
185
181
  class DeprecatedKeyError(BuildCacheBaseException):
@@ -188,25 +184,24 @@ class DeprecatedKeyError(BuildCacheBaseException):
188
184
  name should not appear in the .csv file.
189
185
  """
190
186
 
191
- def __init__(self, fromFile, lineNo, keyType, keyValue, newValue):
192
- super().__init__(fromFile, lineNo,
193
- "{} '{}' is deprecated "
194
- "and should be replaced with '{}'.".format(
195
- keyType, keyValue, newValue
196
- ))
187
+ def __init__(self, fromFile: Path, lineNo: int, keyType: str, keyValue: str, newValue: str) -> None:
188
+ super().__init__(
189
+ fromFile, lineNo,
190
+ f'{keyType} "{keyValue}" is deprecated and should be replaced with "{newValue}".'
191
+ )
197
192
 
198
193
 
199
194
  class MultipleStationEntriesError(DuplicateKeyError):
200
195
  """ Raised when a station appears multiple times in the same file. """
201
196
 
202
- def __init__(self, fromFile, lineNo, facility, prevLineNo):
197
+ def __init__(self, fromFile: Path, lineNo: int, facility: str, prevLineNo: int) -> None:
203
198
  super().__init__(fromFile, lineNo, 'station', facility, prevLineNo)
204
199
 
205
200
 
206
201
  class MultipleItemEntriesError(DuplicateKeyError):
207
202
  """ Raised when one item appears multiple times in the same station. """
208
203
 
209
- def __init__(self, fromFile, lineNo, item, prevLineNo):
204
+ def __init__(self, fromFile: Path, lineNo: int, item: str, prevLineNo: int) -> None:
210
205
  super().__init__(fromFile, lineNo, 'item', item, prevLineNo)
211
206
 
212
207
 
@@ -218,9 +213,8 @@ class SyntaxError(BuildCacheBaseException):
218
213
  text Offending text
219
214
  """
220
215
 
221
- def __init__(self, fromFile, lineNo, problem, text):
222
- error = "{},\ngot: '{}'.".format(problem, text.strip())
223
- super().__init__(fromFile, lineNo, error)
216
+ def __init__(self, fromFile: Path, lineNo: int, problem: str, text: str) -> None:
217
+ super().__init__(fromFile, lineNo, f'{problem},\ngot: "{text.strip()}".')
224
218
 
225
219
 
226
220
  class SupplyError(BuildCacheBaseException):
@@ -228,51 +222,80 @@ class SupplyError(BuildCacheBaseException):
228
222
  Raised when a supply field is incorrectly formatted.
229
223
  """
230
224
 
231
- def __init__(self, fromFile, lineNo, category, problem, value):
232
- error = "Invalid {} supply value: {}. Got: {}". \
233
- format(category, problem, value)
234
- super().__init__(fromFile, lineNo, error)
225
+ def __init__(self, fromFile: Path, lineNo: int, category: str, problem: str, value: Any) -> None:
226
+ super().__init__(fromFile, lineNo, f'Invalid {category} supply value: {problem}. Got: {value}')
227
+
235
228
 
236
229
  ######################################################################
237
230
  # Helpers
238
231
 
239
232
 
240
- def parseSupply(pricesFile, lineNo, category, reading):
233
+ # supply/demand levels are one of '?' for unknown, 'L', 'M' or 'H'
234
+ # for low, medium, or high. We turn these into integer values for
235
+ # ordering convenience, and we include both upper and lower-case
236
+ # so we don't have to sweat ordering.
237
+ #
238
+ SUPPLY_LEVEL_VALUES = {
239
+ '?': -1,
240
+ 'L': 1, 'l': 1,
241
+ 'M': 2, 'm': 2,
242
+ 'H': 3, 'h': 3,
243
+ }
244
+
245
+
246
+ def parseSupply(pricesFile: Path, lineNo: int, category: str, reading: str) -> tuple[int, int]:
247
+ """ Parse a supply specifier which is expected to be in the <number><?, L, M, or H>, and
248
+ returns the units as an integer and a numeric level value suitable for ordering,
249
+ such that ? = -1, L/l = 0, M/m = 1, H/h = 2 """
250
+
251
+ # supply_level <- digit+ level;
252
+ # digit <- [0-9];
253
+ # level <- Unknown / Low / Medium / High;
254
+ # Unknown <- '?';
255
+ # Low <- 'L';
256
+ # Medium <- 'M';
257
+ # High <- 'H';
258
+ if reading == '?':
259
+ return -1, -1
260
+ elif reading == '-':
261
+ return 0, 0
262
+
263
+ # extract the left most digits into unit and the last character into the level reading.
241
264
  units, level = reading[0:-1], reading[-1]
242
- levelNo = "??LMH".find(level.upper()) - 1
243
- if levelNo < -1:
265
+
266
+ # Extract the right most character as the "level" and look up its numeric value.
267
+ levelNo = SUPPLY_LEVEL_VALUES.get(level)
268
+ if levelNo is None:
244
269
  raise SupplyError(
245
270
  pricesFile, lineNo, category, reading,
246
- 'Unrecognized level suffix: "{}": '
247
- "expected one of 'L', 'M', 'H' or '?'".format(
248
- level
249
- )
271
+ f'Unrecognized level suffix: "{level}": expected one of "L", "M", "H" or "?"'
250
272
  )
273
+
274
+ # Expecting a numeric value in units, e.g. 123? -> (units=123, level=?)
251
275
  try:
252
276
  unitsNo = int(units)
253
277
  if unitsNo < 0:
254
- raise ValueError("unsigned unit count")
255
- if unitsNo == 0:
256
- return 0, 0
257
- return unitsNo, levelNo
278
+ # Use the same code-path as if the units fail to parse.
279
+ raise ValueError('negative unit count')
258
280
  except ValueError:
259
- pass
260
-
261
- raise SupplyError(
262
- pricesFile, lineNo, category, reading,
263
- 'Unrecognized units/level value: "{}": '
264
- "expected '-', '?', or a number followed "
265
- "by a level (L, M, H or ?).".format(
266
- level
267
- )
268
- )
281
+ raise SupplyError(
282
+ pricesFile, lineNo, category, reading,
283
+ f'Unrecognized units/level value: "{level}": expected "-", "?", or a number followed by a level (L, M, H or ?).'
284
+ ) from None # don't forward the exception itself
285
+
286
+ # Normalize the units and level when there are no units.
287
+ if unitsNo == 0:
288
+ return 0, 0
289
+
290
+ return unitsNo, levelNo
291
+
269
292
 
270
293
  ######################################################################
271
294
  # Code
272
295
  ######################################################################
273
296
 
274
297
 
275
- def getSystemByNameIndex(cur):
298
+ def getSystemByNameIndex(cur: sqlite3.Cursor) -> dict[str, int]:
276
299
  """ Build station index in STAR/Station notation """
277
300
  cur.execute("""
278
301
  SELECT system_id, UPPER(system.name)
@@ -281,7 +304,7 @@ def getSystemByNameIndex(cur):
281
304
  return { name: ID for (ID, name) in cur }
282
305
 
283
306
 
284
- def getStationByNameIndex(cur):
307
+ def getStationByNameIndex(cur: sqlite3.Cursor) -> dict[str, int]:
285
308
  """ Build station index in STAR/Station notation """
286
309
  cur.execute("""
287
310
  SELECT station_id,
@@ -293,7 +316,7 @@ def getStationByNameIndex(cur):
293
316
  return { name.upper(): ID for (ID, name) in cur }
294
317
 
295
318
 
296
- def getItemByNameIndex(cur):
319
+ def getItemByNameIndex(cur: sqlite3.Cursor) -> dict[str, int]:
297
320
  """
298
321
  Generate item name index.
299
322
  """
@@ -301,10 +324,37 @@ def getItemByNameIndex(cur):
301
324
  return { name: itemID for (itemID, name) in cur }
302
325
 
303
326
 
304
- def processPrices(tdenv, priceFile, db, defaultZero):
327
+ # The return type of process prices is complicated, should probably have been a type
328
+ # in its own right. I'm going to define some aliases to try and persuade IDEs to be
329
+ # more helpful about what it is trying to return.
330
+ if typing.TYPE_CHECKING:
331
+ # A list of the IDs of stations that were modified so they can be updated
332
+ ProcessedStationIds= tuple[tuple[int]]
333
+ ProcessedItem = tuple[
334
+ int, # station ID
335
+ int, # item ID
336
+ Optional[int | float |str], # modified
337
+ int, # demandCR
338
+ int, # demandUnits
339
+ int, # demandLevel
340
+ int, # supplyCr
341
+ int, # supplyUnits
342
+ int, # supplyLevel
343
+ ]
344
+ ProcessedItems = list[ProcessedItem]
345
+ ZeroItems = list[tuple[int, int]] # stationID, itemID
346
+
347
+
348
+ def processPrices(tdenv: TradeEnv, priceFile: Path, db: sqlite3.Connection, defaultZero: bool) -> tuple[ProcessedStationIds, ProcessedItems, ZeroItems, int, int, int, int]:
305
349
  """
306
350
  Yields SQL for populating the database with prices
307
351
  by reading the file handle for price lines.
352
+
353
+ :param tdenv: The environment we're working in
354
+ :param priceFile: File to read
355
+ :param db: SQLite3 database to write to
356
+ :param defaultZero: Whether to create default zero-availability/-demand records for data that's not present
357
+ (if this is a partial update, you don't want this to be False)
308
358
  """
309
359
 
310
360
  DEBUG0, DEBUG1 = tdenv.DEBUG0, tdenv.DEBUG1
@@ -340,20 +390,18 @@ def processPrices(tdenv, priceFile, db, defaultZero):
340
390
  processedSystems = set()
341
391
  processedItems = {}
342
392
  stationItemDates = {}
343
- itemPrefix = ""
344
393
  DELETED = corrections.DELETED
345
- items, zeros, buys, sells = [], [], [], []
394
+ items, zeros = [], []
346
395
 
347
396
  lineNo, localAdd = 0, 0
348
397
  if not ignoreUnknown:
349
-
350
- def ignoreOrWarn(error):
398
+ def ignoreOrWarn(error: Exception) -> None:
351
399
  raise error
352
400
 
353
401
  elif not quiet:
354
402
  ignoreOrWarn = tdenv.WARN
355
403
 
356
- def changeStation(matches):
404
+ def changeStation(matches: re.Match) -> None:
357
405
  nonlocal facility, stationID
358
406
  nonlocal processedStations, processedItems, localAdd
359
407
  nonlocal stationItemDates
@@ -363,20 +411,21 @@ def processPrices(tdenv, priceFile, db, defaultZero):
363
411
  systemNameIn, stationNameIn = matches.group(1, 2)
364
412
  systemName, stationName = systemNameIn.upper(), stationNameIn.upper()
365
413
  corrected = False
366
- facility = "/".join((systemName, stationName))
414
+ facility = f'{systemName}/{stationName}'
367
415
 
368
416
  # Make sure it's valid.
369
417
  stationID = DELETED
370
- newID = stationByName.get(facility, -1)
418
+ newID = stationByName.get(facility, -1) # why -1 and not None?
371
419
  DEBUG0("Selected station: {}, ID={}", facility, newID)
372
420
  if newID is DELETED:
373
421
  DEBUG1("DELETED Station: {}", facility)
374
422
  return
423
+
375
424
  if newID < 0:
376
425
  if utils.checkForOcrDerp(tdenv, systemName, stationName):
377
426
  return
378
427
  corrected = True
379
- altName = sysCorrections.get(systemName, None)
428
+ altName = sysCorrections.get(systemName)
380
429
  if altName is DELETED:
381
430
  DEBUG1("DELETED System: {}", facility)
382
431
  return
@@ -384,21 +433,22 @@ def processPrices(tdenv, priceFile, db, defaultZero):
384
433
  DEBUG1("SYSTEM '{}' renamed '{}'", systemName, altName)
385
434
  systemName, facility = altName, "/".join((altName, stationName))
386
435
 
387
- systemID = systemByName.get(systemName, -1)
436
+ systemID = systemByName.get(systemName, -1) # why -1 and not None?
388
437
  if systemID < 0:
389
438
  ignoreOrWarn(
390
439
  UnknownSystemError(priceFile, lineNo, facility)
391
440
  )
392
441
  return
393
442
 
394
- altStation = stnCorrections.get(facility, None)
395
- if altStation is DELETED:
396
- DEBUG1("DELETED Station: {}", facility)
397
- return
443
+ altStation = stnCorrections.get(facility)
398
444
  if altStation:
445
+ if altStation is DELETED:
446
+ DEBUG1("DELETED Station: {}", facility)
447
+ return
448
+
399
449
  DEBUG1("Station '{}' renamed '{}'", facility, altStation)
400
450
  stationName = altStation.upper()
401
- facility = "/".join((systemName, stationName))
451
+ facility = f'{systemName}/{stationName}'
402
452
 
403
453
  newID = stationByName.get(facility, -1)
404
454
  if newID is DELETED:
@@ -409,7 +459,7 @@ def processPrices(tdenv, priceFile, db, defaultZero):
409
459
  if not ignoreUnknown:
410
460
  DEBUG0(f'Key value: "{list(stationByName.keys())[list(stationByName.values()).index(128893178)]}"')
411
461
  ignoreOrWarn(
412
- UnknownStationError(priceFile, lineNo, facility)
462
+ UnknownStationError(priceFile, lineNo, facility)
413
463
  )
414
464
  return
415
465
  name = utils.titleFixup(stationName)
@@ -430,8 +480,8 @@ def processPrices(tdenv, priceFile, db, defaultZero):
430
480
  """, [systemID, name])
431
481
  newID = inscur.lastrowid
432
482
  stationByName[facility] = newID
433
- tdenv.NOTE("Added local station placeholder for {} (#{})",
434
- facility, newID
483
+ tdenv.NOTE(
484
+ "Added local station placeholder for {} (#{})", facility, newID
435
485
  )
436
486
  localAdd += 1
437
487
  elif newID in processedStations:
@@ -452,7 +502,7 @@ def processPrices(tdenv, priceFile, db, defaultZero):
452
502
  FROM StationItem
453
503
  WHERE station_id = ?
454
504
  """, [stationID])
455
- stationItemDates = {ID: modified for ID, modified in cur}
505
+ stationItemDates = dict(cur)
456
506
 
457
507
  addItem, addZero = items.append, zeros.append
458
508
  getItemID = itemByName.get
@@ -496,7 +546,7 @@ def processPrices(tdenv, priceFile, db, defaultZero):
496
546
  ignoreOrWarn(
497
547
  MultipleItemEntriesError(
498
548
  priceFile, lineNo,
499
- "{}".format(itemName),
549
+ f'{itemName}',
500
550
  processedItems[itemID]
501
551
  )
502
552
  )
@@ -515,26 +565,16 @@ def processPrices(tdenv, priceFile, db, defaultZero):
515
565
  else:
516
566
  newItems += 1
517
567
  if demandString:
518
- if demandString == "?":
519
- demandUnits, demandLevel = -1, -1
520
- elif demandString == "-":
521
- demandUnits, demandLevel = 0, 0
522
- else:
523
- demandUnits, demandLevel = parseSupply(
524
- priceFile, lineNo, 'demand', demandString
525
- )
568
+ demandUnits, demandLevel = parseSupply(
569
+ priceFile, lineNo, 'demand', demandString
570
+ )
526
571
  else:
527
572
  demandUnits, demandLevel = defaultUnits, defaultLevel
528
573
 
529
574
  if demandString and supplyString:
530
- if supplyString == "?":
531
- supplyUnits, supplyLevel = -1, -1
532
- elif supplyString == "-":
533
- supplyUnits, supplyLevel = 0, 0
534
- else:
535
- supplyUnits, supplyLevel = parseSupply(
536
- priceFile, lineNo, 'supply', supplyString
537
- )
575
+ supplyUnits, supplyLevel = parseSupply(
576
+ priceFile, lineNo, 'supply', supplyString
577
+ )
538
578
  else:
539
579
  supplyUnits, supplyLevel = defaultUnits, defaultLevel
540
580
 
@@ -552,32 +592,24 @@ def processPrices(tdenv, priceFile, db, defaultZero):
552
592
  space_cleanup = re.compile(r'\s{2,}').sub
553
593
  for line in priceFile:
554
594
  lineNo += 1
555
- text, _, comment = line.partition('#')
556
- text = text.strip()
557
- # text = space_cleanup(text, ' ').strip()
595
+
596
+ text = line.split('#', 1)[0] # Discard comments
597
+ text = space_cleanup(' ', text).strip() # Remove leading/trailing whitespace, reduce multi-spaces
558
598
  if not text:
559
599
  continue
560
600
 
561
- # replace whitespace with single spaces
562
- if text.find(" "):
563
- # http://stackoverflow.com/questions/2077897
564
- text = ' '.join(text.split())
565
-
566
601
  ########################################
567
602
  # ## "@ STAR/Station" lines.
568
603
  if text.startswith('@'):
569
604
  matches = systemStationRe.match(text)
570
605
  if not matches:
571
- raise SyntaxError("Unrecognized '@' line: {}".format(# pylint: disable=no-value-for-parameter
572
- text
573
- ))
606
+ raise SyntaxError(priceFile, lineNo, "Unrecognized '@' line", text)
574
607
  changeStation(matches)
575
608
  continue
576
609
 
577
610
  if not stationID:
578
611
  # Need a station to process any other type of line.
579
- raise SyntaxError(priceFile, lineNo,
580
- "Expecting '@ SYSTEM / Station' line", text)
612
+ raise SyntaxError(priceFile, lineNo, "Expecting '@ SYSTEM / Station' line", text)
581
613
  if stationID == DELETED:
582
614
  # Ignore all values from a deleted station/system.
583
615
  continue
@@ -592,8 +624,7 @@ def processPrices(tdenv, priceFile, db, defaultZero):
592
624
  # ## "Item sell buy ..." lines.
593
625
  matches = newItemPriceRe.match(text)
594
626
  if not matches:
595
- raise SyntaxError(priceFile, lineNo,
596
- "Unrecognized line/syntax", text)
627
+ raise SyntaxError(priceFile, lineNo, "Unrecognized line/syntax", text)
597
628
 
598
629
  processItemLine(matches)
599
630
 
@@ -610,13 +641,14 @@ def processPrices(tdenv, priceFile, db, defaultZero):
610
641
  stations = tuple((ID,) for ID in processedStations.keys())
611
642
  return stations, items, zeros, newItems, updtItems, ignItems, numSys
612
643
 
644
+
613
645
  ######################################################################
614
646
 
615
647
 
616
- def processPricesFile(tdenv, db, pricesPath, pricesFh = None, defaultZero = False):
648
+ def processPricesFile(tdenv: TradeEnv, db: sqlite3.Connection, pricesPath: Path, pricesFh: Optional[TextIO] = None, defaultZero: bool = False) -> None:
617
649
  tdenv.DEBUG0("Processing Prices file '{}'", pricesPath)
618
650
 
619
- with pricesFh or pricesPath.open('r', encoding = 'utf-8') as pricesFh:
651
+ with pricesFh or pricesPath.open('r', encoding='utf-8') as pricesFh:
620
652
  stations, items, zeros, newItems, updtItems, ignItems, numSys = processPrices(
621
653
  tdenv, pricesFh, db, defaultZero
622
654
  )
@@ -663,7 +695,6 @@ def processPricesFile(tdenv, db, pricesPath, pricesFh = None, defaultZero = Fals
663
695
  # ?, ?, ?
664
696
  # )
665
697
  # """, items)
666
- updatedItems = len(items)
667
698
 
668
699
  tdenv.DEBUG0("Marking populated stations as having a market")
669
700
  db.execute(
@@ -674,8 +705,9 @@ def processPricesFile(tdenv, db, pricesPath, pricesFh = None, defaultZero = Fals
674
705
  ")"
675
706
  )
676
707
 
677
- tdenv.DEBUG0(f'Committing...')
708
+ tdenv.DEBUG0('Committing...')
678
709
  db.commit()
710
+ db.close()
679
711
 
680
712
  changes = " and ".join("{} {}".format(v, k) for k, v in {
681
713
  "new": newItems,
@@ -696,6 +728,7 @@ def processPricesFile(tdenv, db, pricesPath, pricesFh = None, defaultZero = Fals
696
728
  if ignItems:
697
729
  tdenv.NOTE("Ignored {} items with old data", ignItems)
698
730
 
731
+
699
732
  ######################################################################
700
733
 
701
734
 
@@ -749,26 +782,27 @@ def processImportFile(tdenv, db, importPath, tableName):
749
782
  str(importPath), tableName
750
783
  )
751
784
 
752
- fkeySelectStr = ("("
753
- "SELECT {newValue}"
754
- " FROM {table}"
755
- " WHERE {stmt}"
756
- ")"
785
+ fkeySelectStr = (
786
+ "("
787
+ " SELECT {newValue}"
788
+ " FROM {table}"
789
+ " WHERE {stmt}"
790
+ ")"
757
791
  )
758
792
  uniquePfx = "unq:"
759
793
  uniqueLen = len(uniquePfx)
760
794
  ignorePfx = "!"
761
795
 
762
- with importPath.open('r', encoding = 'utf-8') as importFile:
796
+ with importPath.open('r', encoding='utf-8') as importFile:
763
797
  csvin = csv.reader(
764
- importFile, delimiter = ',', quotechar = "'", doublequote = True
798
+ importFile, delimiter=',', quotechar="'", doublequote=True
765
799
  )
766
800
  # first line must be the column names
767
801
  columnDefs = next(csvin)
768
802
  columnCount = len(columnDefs)
769
803
 
770
804
  # split up columns and values
771
- # this is necessqary because the insert might use a foreign key
805
+ # this is necessary because the insert might use a foreign key
772
806
  bindColumns = []
773
807
  bindValues = []
774
808
  joinHelper = []
@@ -817,10 +851,10 @@ def processImportFile(tdenv, db, importPath, tableName):
817
851
  sql_stmt = """
818
852
  INSERT OR REPLACE INTO {table} ({columns}) VALUES({values})
819
853
  """.format(
820
- table = tableName,
821
- columns = ','.join(bindColumns),
822
- values = ','.join(bindValues)
823
- )
854
+ table=tableName,
855
+ columns=','.join(bindColumns),
856
+ values=','.join(bindValues)
857
+ )
824
858
  tdenv.DEBUG0("SQL-Statement: {}", sql_stmt)
825
859
 
826
860
  # Check if there is a deprecation check for this table.
@@ -832,7 +866,7 @@ def processImportFile(tdenv, db, importPath, tableName):
832
866
 
833
867
  # import the data
834
868
  importCount = 0
835
- uniqueIndex = dict()
869
+ uniqueIndex = {}
836
870
 
837
871
  for linein in csvin:
838
872
  if not linein:
@@ -968,6 +1002,7 @@ def buildCache(tdb, tdenv):
968
1002
 
969
1003
  tempDB.commit()
970
1004
  tempDB.close()
1005
+ tdb.close()
971
1006
 
972
1007
  tdenv.DEBUG0("Swapping out db files")
973
1008
 
@@ -1021,7 +1056,7 @@ def importDataFromFile(tdb, tdenv, path, pricesFh = None, reset = False):
1021
1056
  db = tdb.getDB(),
1022
1057
  pricesPath = path,
1023
1058
  pricesFh = pricesFh,
1024
- )
1059
+ )
1025
1060
 
1026
1061
  # If everything worked, we may need to re-build the prices file.
1027
1062
  if path != tdb.pricesPath: