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.
- tradedangerous/__init__.py +4 -4
- tradedangerous/cache.py +183 -148
- tradedangerous/cli.py +2 -7
- tradedangerous/commands/TEMPLATE.py +1 -2
- tradedangerous/commands/__init__.py +2 -4
- tradedangerous/commands/buildcache_cmd.py +6 -11
- tradedangerous/commands/buy_cmd.py +11 -12
- tradedangerous/commands/commandenv.py +16 -15
- tradedangerous/commands/exceptions.py +6 -4
- tradedangerous/commands/export_cmd.py +2 -4
- tradedangerous/commands/import_cmd.py +3 -5
- tradedangerous/commands/local_cmd.py +16 -25
- tradedangerous/commands/market_cmd.py +9 -8
- tradedangerous/commands/nav_cmd.py +17 -25
- tradedangerous/commands/olddata_cmd.py +9 -15
- tradedangerous/commands/parsing.py +9 -6
- tradedangerous/commands/rares_cmd.py +9 -10
- tradedangerous/commands/run_cmd.py +25 -26
- tradedangerous/commands/sell_cmd.py +9 -9
- tradedangerous/commands/shipvendor_cmd.py +4 -7
- tradedangerous/commands/station_cmd.py +8 -14
- tradedangerous/commands/trade_cmd.py +5 -10
- tradedangerous/commands/update_cmd.py +10 -7
- tradedangerous/commands/update_gui.py +1 -3
- tradedangerous/corrections.py +1 -3
- tradedangerous/csvexport.py +8 -8
- tradedangerous/edscupdate.py +4 -6
- tradedangerous/edsmupdate.py +4 -4
- tradedangerous/formatting.py +53 -40
- tradedangerous/fs.py +6 -6
- tradedangerous/gui.py +53 -62
- tradedangerous/jsonprices.py +8 -16
- tradedangerous/mapping.py +4 -3
- tradedangerous/mfd/__init__.py +2 -4
- tradedangerous/mfd/saitek/__init__.py +0 -1
- tradedangerous/mfd/saitek/directoutput.py +8 -11
- tradedangerous/mfd/saitek/x52pro.py +5 -7
- tradedangerous/misc/checkpricebounds.py +2 -3
- tradedangerous/misc/clipboard.py +2 -3
- tradedangerous/misc/coord64.py +2 -1
- tradedangerous/misc/derp-sentinel.py +1 -1
- tradedangerous/misc/diff-system-csvs.py +3 -0
- tradedangerous/misc/eddb.py +1 -3
- tradedangerous/misc/eddn.py +2 -2
- tradedangerous/misc/edsc.py +7 -14
- tradedangerous/misc/edsm.py +1 -8
- tradedangerous/misc/importeddbstats.py +2 -1
- tradedangerous/misc/prices-json-exp.py +7 -5
- tradedangerous/misc/progress.py +2 -2
- tradedangerous/plugins/__init__.py +2 -2
- tradedangerous/plugins/edapi_plug.py +13 -19
- tradedangerous/plugins/edcd_plug.py +4 -5
- tradedangerous/plugins/eddblink_plug.py +14 -17
- tradedangerous/plugins/edmc_batch_plug.py +3 -5
- tradedangerous/plugins/journal_plug.py +2 -1
- tradedangerous/plugins/netlog_plug.py +5 -5
- tradedangerous/plugins/spansh_plug.py +393 -176
- tradedangerous/prices.py +19 -20
- tradedangerous/submit-distances.py +3 -8
- tradedangerous/templates/TradeDangerous.sql +305 -306
- tradedangerous/trade.py +12 -5
- tradedangerous/tradecalc.py +30 -34
- tradedangerous/tradedb.py +140 -206
- tradedangerous/tradeenv.py +143 -69
- tradedangerous/tradegui.py +4 -2
- tradedangerous/transfers.py +23 -20
- tradedangerous/version.py +1 -1
- {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/METADATA +2 -2
- tradedangerous-11.0.0.dist-info/RECORD +79 -0
- tradedangerous-10.16.17.dist-info/RECORD +0 -79
- {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/LICENSE +0 -0
- {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/WHEEL +0 -0
- {tradedangerous-10.16.17.dist-info → tradedangerous-11.0.0.dist-info}/entry_points.txt +0 -0
- {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
|
|
24
|
-
from pathlib import Path
|
|
25
|
-
from .tradeexcept import TradeException
|
|
23
|
+
from __future__ import annotations
|
|
26
24
|
|
|
27
|
-
from
|
|
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
|
-
{
|
|
88
|
+
{itemPriceFrag}
|
|
80
89
|
(
|
|
81
90
|
\s+
|
|
82
91
|
# demand units and level
|
|
83
|
-
(?P<demand> {
|
|
92
|
+
(?P<demand> {qtyLevelFrag})
|
|
84
93
|
\s+
|
|
85
94
|
# supply units and level
|
|
86
|
-
(?P<supply> {
|
|
95
|
+
(?P<supply> {qtyLevelFrag})
|
|
87
96
|
# time is optional
|
|
88
97
|
(?:
|
|
89
98
|
\s+
|
|
90
|
-
{
|
|
99
|
+
{timeFrag}
|
|
91
100
|
)?
|
|
92
101
|
)?
|
|
93
102
|
\s*
|
|
94
103
|
$
|
|
95
|
-
"""
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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__(
|
|
180
|
-
|
|
181
|
-
|
|
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__(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
434
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
556
|
-
text =
|
|
557
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
|
796
|
+
with importPath.open('r', encoding='utf-8') as importFile:
|
|
763
797
|
csvin = csv.reader(
|
|
764
|
-
importFile, delimiter
|
|
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
|
|
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
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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 =
|
|
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:
|