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