tradedangerous 11.0.3__py3-none-any.whl → 11.0.4__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/cache.py CHANGED
@@ -206,7 +206,7 @@ class MultipleItemEntriesError(DuplicateKeyError):
206
206
  super().__init__(fromFile, lineNo, 'item', item, prevLineNo)
207
207
 
208
208
 
209
- class SyntaxError(BuildCacheBaseException):
209
+ class InvalidLineError(BuildCacheBaseException):
210
210
  """
211
211
  Raised when an invalid line is read.
212
212
  Attributes:
@@ -258,7 +258,7 @@ def parseSupply(pricesFile: Path, lineNo: int, category: str, reading: str) -> t
258
258
  # High <- 'H';
259
259
  if reading == '?':
260
260
  return -1, -1
261
- elif reading == '-':
261
+ if reading == '-':
262
262
  return 0, 0
263
263
 
264
264
  # extract the left most digits into unit and the last character into the level reading.
@@ -604,13 +604,13 @@ def processPrices(tdenv: TradeEnv, priceFile: Path, db: sqlite3.Connection, defa
604
604
  if text.startswith('@'):
605
605
  matches = systemStationRe.match(text)
606
606
  if not matches:
607
- raise SyntaxError(priceFile, lineNo, "Unrecognized '@' line", text)
607
+ raise InvalidLineError(priceFile, lineNo, "Unrecognized '@' line", text)
608
608
  changeStation(matches)
609
609
  continue
610
610
 
611
611
  if not stationID:
612
612
  # Need a station to process any other type of line.
613
- raise SyntaxError(priceFile, lineNo, "Expecting '@ SYSTEM / Station' line", text)
613
+ raise InvalidLineError(priceFile, lineNo, "Expecting '@ SYSTEM / Station' line", text)
614
614
  if stationID == DELETED:
615
615
  # Ignore all values from a deleted station/system.
616
616
  continue
@@ -625,7 +625,7 @@ def processPrices(tdenv: TradeEnv, priceFile: Path, db: sqlite3.Connection, defa
625
625
  # ## "Item sell buy ..." lines.
626
626
  matches = newItemPriceRe.match(text)
627
627
  if not matches:
628
- raise SyntaxError(priceFile, lineNo, "Unrecognized line/syntax", text)
628
+ raise InvalidLineError(priceFile, lineNo, "Unrecognized line/syntax", text)
629
629
 
630
630
  processItemLine(matches)
631
631
 
@@ -639,7 +639,7 @@ def processPrices(tdenv: TradeEnv, priceFile: Path, db: sqlite3.Connection, defa
639
639
  "if you /need/ to persist them."
640
640
  )
641
641
 
642
- stations = tuple((ID,) for ID in processedStations.keys())
642
+ stations = tuple((ID,) for ID in processedStations)
643
643
  return stations, items, zeros, newItems, updtItems, ignItems, numSys
644
644
 
645
645
 
@@ -649,9 +649,9 @@ def processPrices(tdenv: TradeEnv, priceFile: Path, db: sqlite3.Connection, defa
649
649
  def processPricesFile(tdenv: TradeEnv, db: sqlite3.Connection, pricesPath: Path, pricesFh: Optional[TextIO] = None, defaultZero: bool = False) -> None:
650
650
  tdenv.DEBUG0("Processing Prices file '{}'", pricesPath)
651
651
 
652
- with pricesFh or pricesPath.open('r', encoding='utf-8') as pricesFh:
652
+ with (pricesFh or pricesPath.open('r', encoding='utf-8')) as fh:
653
653
  stations, items, zeros, newItems, updtItems, ignItems, numSys = processPrices(
654
- tdenv, pricesFh, db, defaultZero
654
+ tdenv, fh, db, defaultZero
655
655
  )
656
656
 
657
657
  if not tdenv.mergeImport:
@@ -907,7 +907,7 @@ def processImportFile(tdenv, db, importPath, tableName):
907
907
  try:
908
908
  db.execute(sql_stmt, linein)
909
909
  importCount += 1
910
- except Exception as e:
910
+ except Exception as e: # pylint: disable=broad-exception-caught
911
911
  tdenv.WARN(
912
912
  "*** INTERNAL ERROR: {err}\n"
913
913
  "CSV File: {file}:{line}\n"
tradedangerous/cli.py CHANGED
@@ -32,34 +32,35 @@
32
32
  # to empower other programmers to do cool stuff.
33
33
 
34
34
  import os
35
+ import sys
35
36
  import traceback
36
37
 
37
38
  from . import commands
39
+ from . import tradeexcept
38
40
  from .commands import exceptions
39
41
  from .plugins import PluginException
40
42
 
41
-
42
43
  from . import tradedb
43
44
 
45
+ if "CPROF" in os.environ:
46
+ import cProfile
47
+
44
48
 
45
49
  def main(argv = None):
46
- import sys
47
50
  if not argv:
48
51
  argv = sys.argv
49
- if sys.hexversion < 0x03040200:
52
+ if sys.hexversion < 0x03070000:
50
53
  raise SystemExit(
51
- "Sorry: TradeDangerous requires Python 3.4.2 or higher.\n"
54
+ "Sorry: TradeDangerous requires Python 3.7 or higher.\n"
52
55
  "For assistance, see:\n"
53
56
  "\tBug Tracker: https://github.com/eyeonus/Trade-Dangerous/issues\n"
54
57
  "\tDocumentation: https://github.com/eyeonus/Trade-Dangerous/wiki\n"
55
58
  "\tEDForum Thread: https://forums.frontier.co.uk/showthread.php/441509\n"
56
59
  )
57
- from . import tradeexcept
58
60
 
59
61
  try:
60
62
  try:
61
63
  if "CPROF" in os.environ:
62
- import cProfile
63
64
  cProfile.run("trade(argv)")
64
65
  else:
65
66
  trade(argv)
@@ -79,7 +80,7 @@ def main(argv = None):
79
80
  print()
80
81
  print(traceback.format_exc())
81
82
  print(
82
- "Please report this bug (http://kfs.org/td/issues). You may be "
83
+ "Please report this bug (http://github.com/eyeonus/Trade-Dangerous/issues). You may be "
83
84
  "able to work around it by using the '-q' parameter. Windows "
84
85
  "users may be able to use 'chcp.com 65001' to tell the console "
85
86
  "you want to support UTF-8 characters."
@@ -36,6 +36,7 @@ def search_keyList(items, val):
36
36
  for row in items:
37
37
  if row['from'] == row['to'] == val:
38
38
  return row
39
+ return None
39
40
 
40
41
  def getUniqueIndex(conn, tableName):
41
42
  """ return all unique columns """
@@ -8,7 +8,9 @@ import itertools
8
8
  import typing
9
9
 
10
10
  if typing.TYPE_CHECKING:
11
- from typing import Any, Callable, Optional
11
+ from typing import Any, Optional
12
+ from collections.abc import Callable
13
+
12
14
 
13
15
  class ColumnFormat:
14
16
  """
@@ -197,8 +199,7 @@ if __name__ == '__main__':
197
199
  print("Simple usage:")
198
200
  present()
199
201
 
200
- print()
201
- print("Adding age ColumnFormat:")
202
-
203
- rowFmt.append(after='Name', col=ColumnFormat("Age", '>', 3, pre='|', post='|', key=lambda row: row['age']))
204
- present()
202
+ #print()
203
+ #print("Adding age ColumnFormat:")
204
+ #rowFmt.append(after='Name', col=ColumnFormat("Age", '>', 3, pre='|', post='|', key=lambda row: row['age']))
205
+ #present()
@@ -1,7 +1,8 @@
1
- # ----------------------------------------------------------------
2
- # Import plugin that uses data files from EDDB.io and (optionally)
3
- # a EDDBlink_listener server to update the Database.
4
- # ----------------------------------------------------------------
1
+ """
2
+ Import plugin that uses data files from
3
+ https://elite.tromador.com/ to update the Database.
4
+ """
5
+ from __future__ import annotations
5
6
  import certifi
6
7
  import csv
7
8
  import datetime
@@ -10,42 +11,48 @@ import os
10
11
  import sqlite3
11
12
  import ssl
12
13
  import time
14
+ import typing
13
15
 
14
16
  from urllib import request
15
- from calendar import timegm
16
17
  from pathlib import Path
17
18
 
18
19
  from .. import plugins, cache, transfers
19
20
  from ..misc import progress as pbar
20
21
  from ..plugins import PluginException
21
22
 
23
+
24
+ if typing.TYPE_CHECKING:
25
+ from typing import Optional
26
+ from .. tradeenv import TradeEnv
27
+
28
+
22
29
  # Constants
23
30
  BASE_URL = os.environ.get('TD_SERVER') or "https://elite.tromador.com/files/"
24
31
  CONTEXT=ssl.create_default_context(cafile=certifi.where())
25
32
 
26
33
 
27
- def request_url(url, headers=None):
34
+ def _request_url(url, headers=None):
28
35
  data = None
29
36
  if headers:
30
37
  data = bytes(json.dumps(headers), encoding="utf-8")
31
38
 
32
- return request.urlopen(request.Request(url, data=data), context=CONTEXT)
39
+ return request.urlopen(request.Request(url, data=data), context=CONTEXT, timeout=90)
33
40
 
34
41
 
35
42
  class DecodingError(PluginException):
36
43
  pass
37
44
 
38
45
 
39
- def file_line_count(from_file: Path, bufsize: int = 128 * 1024) -> int:
46
+ def _file_line_count(from_file: Path, bufsize: int = 128 * 1024) -> int:
40
47
  """ counts the number of newline characters in a given file. """
41
- # Pre-allocate a buffer so we're not putting pressure on the garbage collector,
42
- # capture it's counting method so we don't have to keep looking that up on
43
- # large files.
48
+ # Pre-allocate a buffer so we aren't putting pressure on the garbage collector.
44
49
  buf = bytearray(bufsize)
50
+
51
+ # Capture it's counting method, so we don't have to keep looking that up on
52
+ # large files.
45
53
  counter = buf.count
46
-
54
+
47
55
  total = 0
48
-
49
56
  with from_file.open("rb") as fh:
50
57
  # Capture the 'readinto' method to avoid lookups.
51
58
  reader = fh.readinto
@@ -60,10 +67,46 @@ def file_line_count(from_file: Path, bufsize: int = 128 * 1024) -> int:
60
67
  # when 0 <= read < bufsize we're on the last page of the
61
68
  # file, so we need to take a slice of the buffer, which creates
62
69
  # a new object and thus we also have to lookup count. it's trivial
63
- # but if you have to do it 10,000x it's definitly not a rounding error.
70
+ # but if you have to do it 10,000x it's definitely not a rounding error.
64
71
  return total + buf[:read].count(b'\n')
65
72
 
66
73
 
74
+ def _count_listing_entries(tdenv: TradeEnv, listings: Path) -> int:
75
+ """ Calculates the number of entries in a listing file by counting the lines. """
76
+ if not listings.exists():
77
+ tdenv.NOTE("File not found, aborting: {}", listings)
78
+ return 0
79
+
80
+ tdenv.DEBUG0(f"Getting total number of entries in {listings}...")
81
+ count = _file_line_count(listings)
82
+ if count <= 1:
83
+ if count == 1:
84
+ tdenv.DEBUG0("Listing count of 1 suggests nothing but a header")
85
+ else:
86
+ tdenv.DEBUG0("Listings file is empty, nothing to do.")
87
+ return 0
88
+
89
+ return count + 1 # kfsone: Doesn't the header already make this + 1?
90
+
91
+
92
+ def _make_item_id_lookup(tdenv: TradeEnv, db: sqlite3.Cursor) -> frozenset[int]:
93
+ """ helper: retrieve the list of commodities in database. """
94
+ tdenv.DEBUG0("Getting list of commodities...")
95
+ return frozenset(cols[0] for cols in db.execute("SELECT item_id FROM Item"))
96
+
97
+
98
+ def _make_station_id_lookup(tdenv: TradeEnv, db: sqlite3.Cursor) -> frozenset[int]:
99
+ """ helper: retrieve the list of station IDs in database. """
100
+ tdenv.DEBUG0("Getting list of stations...")
101
+ return frozenset(cols[0] for cols in db.execute("SELECT station_id FROM Station"))
102
+
103
+
104
+ def _collect_station_modified_times(tdenv: TradeEnv, db: sqlite3.Cursor) -> dict[int, int]:
105
+ """ helper: build a list of the last modified time for all stations by id. """
106
+ tdenv.DEBUG0("Getting last-update times for stations...")
107
+ return dict(db.execute("SELECT station_id, strftime('%s', MIN(modified)) FROM StationItem GROUP BY station_id"))
108
+
109
+
67
110
  class ImportPlugin(plugins.ImportPluginBase):
68
111
  """
69
112
  Plugin that downloads data from eddb.
@@ -113,61 +156,25 @@ class ImportPlugin(plugins.ImportPluginBase):
113
156
  def now(self):
114
157
  return datetime.datetime.now()
115
158
 
116
- def execute(self, sql_cmd, args = None):
117
- cur = self.tdb.getDB().cursor()
118
-
119
- self.tdenv.DEBUG2(f"SQL-Statement:\n'{sql_cmd},{args}'")
120
- success = False
121
- result = None
122
- while not success:
123
- try:
124
- if args:
125
- result = cur.execute(sql_cmd, args)
126
- else:
127
- result = cur.execute(sql_cmd)
128
- success = True
129
- except sqlite3.OperationalError as e:
130
- if "locked" not in str(e):
131
- success = True
132
- raise sqlite3.OperationalError(e)
133
- else:
134
- print("(execute) Database is locked, waiting for access.", end = "\r")
135
- time.sleep(1)
136
- return result
137
-
138
- @staticmethod
139
- def fetchIter(cursor, arraysize = 1000):
140
- """
141
- An iterator that uses fetchmany to keep memory usage down
142
- and speed up the time to retrieve the results dramatically.
143
- """
144
- while True:
145
- results = cursor.fetchmany(arraysize)
146
- if not results:
147
- break
148
- for result in results:
149
- yield result
150
-
151
-
152
159
  def downloadFile(self, path):
153
160
  """
154
161
  Fetch the latest dumpfile from the website if newer than local copy.
155
162
  """
156
163
 
157
164
  def openURL(url):
158
- return request_url(url, headers = {'User-Agent': 'Trade-Dangerous'})
165
+ return _request_url(url, headers = {'User-Agent': 'Trade-Dangerous'})
159
166
 
160
- if path != self.liveListingsPath and path != self.listingsPath:
161
- localPath = self.tdb.dataPath / path
167
+ if path not in (self.liveListingsPath, self.listingsPath):
168
+ localPath = Path(self.tdb.dataPath, path)
162
169
  else:
163
- localPath = self.dataPath / path
170
+ localPath = Path(self.dataPath, path)
164
171
 
165
172
  url = BASE_URL + str(path)
166
173
 
167
174
  self.tdenv.NOTE("Checking for update to '{}'.", path)
168
175
  try:
169
176
  response = openURL(url)
170
- except Exception as e:
177
+ except Exception as e: # pylint: disable=broad-exception-caught
171
178
  self.tdenv.WARN("Problem with download:\n URL: {}\n Error: {}", BASE_URL + str(path), str(e))
172
179
  return False
173
180
 
@@ -190,148 +197,137 @@ class ImportPlugin(plugins.ImportPluginBase):
190
197
  Purges systems from the System table that do not have any stations claiming to be in them.
191
198
  Keeps table from becoming too large because of fleet carriers moving to unpopulated systems.
192
199
  """
193
-
200
+ db = self.tdb.getDB()
194
201
  self.tdenv.NOTE("Purging Systems with no stations: Start time = {}", self.now())
195
-
196
- self.execute("PRAGMA foreign_keys = OFF")
197
-
198
- print("Saving systems with stations.... " + str(self.now()) + "\t\t\t\t", end="\r")
199
- self.execute("DROP TABLE IF EXISTS System_copy")
200
- self.execute("""CREATE TABLE System_copy AS SELECT * FROM System
202
+
203
+ db.execute("PRAGMA foreign_keys = OFF")
204
+
205
+ self.tdenv.DEBUG0("Saving systems with stations.... " + str(self.now()) + "\t\t\t\t", end="\r")
206
+ db.execute("DROP TABLE IF EXISTS System_copy")
207
+ db.execute("""CREATE TABLE System_copy AS SELECT * FROM System
201
208
  WHERE system_id IN (SELECT system_id FROM Station)
202
209
  """)
203
-
204
- print("Erasing table and reinserting kept systems.... " + str(self.now()) + "\t\t\t\t", end="\r")
205
- self.execute("DELETE FROM System")
206
- self.execute("INSERT INTO System SELECT * FROM System_copy")
207
-
208
- print("Removing copy.... " + str(self.now()) + "\t\t\t\t", end="\r")
209
- self.execute("PRAGMA foreign_keys = ON")
210
- self.execute("DROP TABLE IF EXISTS System_copy")
211
-
210
+
211
+ self.tdenv.DEBUG0("Erasing table and reinserting kept systems.... " + str(self.now()) + "\t\t\t\t", end="\r")
212
+ db.execute("DELETE FROM System")
213
+ db.execute("INSERT INTO System SELECT * FROM System_copy")
214
+
215
+ self.tdenv.DEBUG0("Removing copy.... " + str(self.now()) + "\t\t\t\t", end="\r")
216
+ db.execute("PRAGMA foreign_keys = ON")
217
+ db.execute("DROP TABLE IF EXISTS System_copy")
218
+
219
+ db.commit()
220
+
212
221
  self.tdenv.NOTE("Finished purging Systems. End time = {}", self.now())
213
222
 
214
- def commit(self):
215
- success = False
216
- while not success:
217
- try:
218
- self.tdb.getDB().commit()
219
- success = True
220
- except sqlite3.OperationalError:
221
- print("(commit) Database is locked, waiting for access.", end = "\r")
222
- time.sleep(1)
223
-
224
223
  def importListings(self, listings_file):
225
224
  """
226
- Updates the market data (AKA the StationItem table) using listings.csv
225
+ Updates the market data (AKA the StationItem table) using listings_file
227
226
  Writes directly to database.
228
227
  """
228
+ listings_path = Path(self.dataPath, listings_file).absolute()
229
+ from_live = listings_path != Path(self.dataPath, self.listingsPath).absolute()
230
+ self.tdenv.NOTE("Processing market data from {}: Start time = {}. Live = {}", listings_file, self.now(), from_live)
229
231
 
230
- self.tdenv.NOTE("Processing market data from {}: Start time = {}", listings_file, self.now())
231
- if not (self.dataPath / listings_file).exists():
232
- self.tdenv.NOTE("File not found, aborting: {}", (self.dataPath / listings_file))
232
+ total = _count_listing_entries(self.tdenv, listings_path)
233
+ if not total:
233
234
  return
235
+
236
+ stmt_unliven_station = """UPDATE StationItem SET from_live = 0 WHERE station_id = ?"""
237
+ stmt_flush_station = """DELETE from StationItem WHERE station_id = ?"""
238
+ stmt_add_listing = """
239
+ INSERT OR IGNORE INTO StationItem (
240
+ station_id, item_id, modified, from_live,
241
+ demand_price, demand_units, demand_level,
242
+ supply_price, supply_units, supply_level
243
+ )
244
+ VALUES (
245
+ ?, ?, datetime(?, 'unixepoch'), ?,
246
+ ?, ?, ?,
247
+ ?, ?, ?
248
+ )
249
+ """
234
250
 
235
- total = 1
236
-
237
- from_live = 0 if listings_file == self.listingsPath else 1
238
-
239
- self.tdenv.DEBUG0(f"Getting total number of entries in {listings_file}...")
240
- listings_path = Path(self.dataPath, listings_file)
241
- total += file_line_count(listings_path)
242
-
243
- liveStmt = """UPDATE StationItem
244
- SET from_live = 0
245
- WHERE station_id = ?"""
246
-
247
- delStmt = "DELETE from StationItem WHERE station_id = ?"
248
-
249
- listingStmt = """INSERT OR IGNORE INTO StationItem
250
- (station_id, item_id, modified,
251
- demand_price, demand_units, demand_level,
252
- supply_price, supply_units, supply_level, from_live)
253
- VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )"""
254
-
255
- self.tdenv.DEBUG0("Getting list of commodities...")
256
- items = [cols[0] for cols in self.execute("SELECT item_id FROM Item ORDER BY item_id")]
257
-
258
- self.tdenv.DEBUG0("Getting list of stations...")
259
- stationList = {
260
- stationID
261
- for (stationID,) in self.execute("SELECT station_id FROM Station")
262
- }
263
-
264
- stationItems = dict(self.execute('SELECT station_id, UNIXEPOCH(modified) FROM StationItem').fetchall())
251
+ # Fetch all the items IDS
252
+ db = self.tdb.getDB()
253
+ item_lookup = _make_item_id_lookup(self.tdenv, db.cursor())
254
+ station_lookup = _make_station_id_lookup(self.tdenv, db.cursor())
255
+ last_station_update_times = _collect_station_modified_times(self.tdenv, db.cursor())
265
256
 
257
+ cur_station = None
266
258
  self.tdenv.DEBUG0("Processing entries...")
267
259
  with listings_path.open("r", encoding="utf-8", errors="ignore") as fh:
268
260
  prog = pbar.Progress(total, 50)
269
- listings = csv.DictReader(fh)
270
-
271
- cur_station = -1
261
+
262
+ cursor: Optional[sqlite3.Cursor] = db.cursor()
272
263
 
273
- for listing in listings:
274
- if prog.increment(1, postfix = lambda value, total: f" {(value / total * 100):.0f}% {value} / {total}"):
275
- # Do a commit and close the DB every 2%.
276
- # This ensures the listings are put in the DB and the WAL is cleared.
277
- self.commit()
278
- self.tdb.close()
264
+ for listing in csv.DictReader(fh):
265
+ prog.increment(1, postfix = lambda value, total: f" {(value / total * 100):.0f}% {value} / {total}")
279
266
 
280
267
  station_id = int(listing['station_id'])
281
- if station_id not in stationList:
268
+ if station_id not in station_lookup:
282
269
  continue
283
270
 
271
+ listing_time = int(listing['collected_at'])
272
+
284
273
  if station_id != cur_station:
285
- cur_station = station_id
286
- skipStation = False
274
+ # commit anything from the previous station, get a new cursor
275
+ db.commit()
276
+ cur_station, skip_station, cursor = station_id, False, db.cursor()
287
277
 
288
278
  # Check if listing already exists in DB and needs updated.
289
- if stationItems.get(station_id):
279
+ last_modified: int = int(last_station_update_times.get(station_id, 0))
280
+ if last_modified:
290
281
  # When the listings.csv data matches the database, update to make from_live == 0.
291
- if int(listing['collected_at']) == stationItems.get(station_id) and not from_live:
292
- self.tdenv.DEBUG1(f"Marking {cur_station} as no longer 'live'.")
293
- self.execute(liveStmt, (cur_station,))
282
+ if listing_time == last_modified and not from_live:
283
+ self.tdenv.DEBUG1(f"Marking {cur_station} as no longer 'live' (old={last_modified}, listing={listing_time}).")
284
+ cursor.execute(stmt_unliven_station, (cur_station,))
285
+ skip_station = True
286
+ continue
287
+
294
288
  # Unless the import file data is newer, nothing else needs to be done for this station,
295
289
  # so the rest of the listings for this station can be skipped.
296
- if int(listing['collected_at']) <= stationItems.get(station_id):
297
- skipStation = True
290
+ if listing_time <= last_modified:
291
+ skip_station = True
298
292
  continue
299
293
 
300
294
  # The data from the import file is newer, so we need to delete the old data for this station.
301
- self.tdenv.DEBUG1(f"Deleting old listing data for {cur_station}.")
302
- self.execute(delStmt, (cur_station,))
303
- # We've deleted all the items from this station, so remove it.
304
- del stationItems[station_id]
305
-
295
+ self.tdenv.DEBUG1(f"Deleting old listing data for {cur_station} (old={last_modified}, listing={listing_time}).")
296
+ cursor.execute(stmt_flush_station, (cur_station,))
297
+ last_station_update_times[station_id] = listing_time
306
298
 
307
- if skipStation:
299
+ # station skip lasts until we change station id.
300
+ if skip_station:
308
301
  continue
309
302
 
310
303
  # Since this station is not being skipped, get the data and prepare for insertion into the DB.
311
304
  item_id = int(listing['commodity_id'])
312
305
  # listings.csv includes rare items, which we are ignoring.
313
- if item_id not in items:
306
+ if item_id not in item_lookup:
314
307
  continue
315
- modified = datetime.datetime.utcfromtimestamp(int(listing['collected_at'])).strftime('%Y-%m-%d %H:%M:%S')
308
+
316
309
  demand_price = int(listing['sell_price'])
317
310
  demand_units = int(listing['demand'])
318
- demand_level = int(listing['demand_bracket']) if listing['demand_bracket'] != '' else -1
311
+ demand_level = int(listing.get('demand_bracket') or '-1')
319
312
  supply_price = int(listing['buy_price'])
320
313
  supply_units = int(listing['supply'])
321
- supply_level = int(listing['supply_bracket']) if listing['supply_bracket'] != '' else -1
314
+ supply_level = int(listing.get('supply_bracket') or '-1')
322
315
 
323
316
  self.tdenv.DEBUG1(f"Inserting new listing data for {station_id}.")
324
- self.execute(listingStmt, (station_id, item_id, modified,
325
- demand_price, demand_units, demand_level,
326
- supply_price, supply_units, supply_level, from_live))
317
+ cursor.execute(stmt_add_listing, (
318
+ station_id, item_id, listing_time, from_live,
319
+ demand_price, demand_units, demand_level,
320
+ supply_price, supply_units, supply_level,
321
+ ))
327
322
 
328
- # Do a final commit to be sure
329
- self.commit()
330
- self.tdb.close()
331
-
332
- while prog.value < prog.maxValue:
333
- prog.increment(1, postfix = lambda value, total: " " + str(round(value / total * 100)) + "%")
334
- prog.clear()
323
+ prog.clear()
324
+
325
+ # Do a final commit to be sure
326
+ db.commit()
327
+
328
+ self.tdenv.NOTE("Optimizing database...")
329
+ db.execute("VACUUM")
330
+ self.tdb.close()
335
331
 
336
332
  self.tdenv.NOTE("Finished processing market data. End time = {}", self.now())
337
333
 
@@ -347,14 +343,13 @@ class ImportPlugin(plugins.ImportPluginBase):
347
343
  # have been passed, enable 'listings'.
348
344
  default = True
349
345
  for option in self.options:
350
- # if not option in ('force', 'fallback', 'skipvend', 'progbar'):
351
- if option not in ('force', 'skipvend'):
346
+ if option not in ('force', 'skipvend', 'purge'):
352
347
  default = False
353
348
  if default:
354
349
  self.options["listings"] = True
355
350
 
356
- # We can probably safely assume that the plugin has never been run if
357
- # the prices file doesn't exist, since the plugin always generates it.
351
+ # We can probably safely assume that the plugin
352
+ # has never been run if the db file doesn't exist.
358
353
  if not (self.tdb.dataPath / Path("TradeDangerous.db")).exists():
359
354
  self.options["clean"] = True
360
355
 
@@ -497,7 +492,6 @@ class ImportPlugin(plugins.ImportPluginBase):
497
492
 
498
493
  if self.getOption("purge"):
499
494
  self.purgeSystems()
500
- # self.commit()
501
495
 
502
496
  if self.getOption("listings"):
503
497
  if self.downloadFile(self.listingsPath) or self.getOption("force"):
@@ -1,26 +1,29 @@
1
+ """ Plugin for importing data from spansh """
1
2
  from __future__ import annotations
2
3
 
4
+ from collections import namedtuple
3
5
  from contextlib import contextmanager
4
6
  from datetime import datetime, timedelta
5
7
  from pathlib import Path
8
+ from rich.progress import Progress
6
9
 
10
+ from .. import plugins, cache, transfers, csvexport, corrections
11
+
12
+ import sqlite3
7
13
  import sys
8
14
  import time
9
15
  import typing
10
- from collections import namedtuple
16
+
17
+ import ijson
18
+
11
19
  if sys.version_info.major == 3 and sys.version_info.minor >= 10:
12
20
  from dataclasses import dataclass
13
21
  else:
14
22
  dataclass = False # pylint: disable=invalid-name
15
23
 
16
- from rich.progress import Progress
17
- import ijson
18
- import sqlite3
19
-
20
- from .. import plugins, cache, transfers, csvexport, corrections
21
-
22
24
  if typing.TYPE_CHECKING:
23
- from typing import Any, Iterable, Optional
25
+ from typing import Any, Optional
26
+ from collections.abc import Iterable
24
27
  from .. tradeenv import TradeEnv
25
28
 
26
29
  SOURCE_URL = 'https://downloads.spansh.co.uk/galaxy_stations.json'
@@ -237,11 +240,11 @@ class ImportPlugin(plugins.ImportPluginBase):
237
240
  def commit(self, *, force: bool = False) -> None:
238
241
  """ Perform a commit if required, but try not to do a crazy amount of committing. """
239
242
  if not force and not self.need_commit:
240
- return self.cursor
243
+ return
241
244
 
242
245
  if not force and self.commit_limit > 0:
243
246
  self.commit_limit -= 1
244
- return self.cursor
247
+ return
245
248
 
246
249
  db = self.tdb.getDB()
247
250
  db.commit()
@@ -392,7 +395,7 @@ class ImportPlugin(plugins.ImportPluginBase):
392
395
  categories.setdefault(commodity.category, []).append(commodity)
393
396
  return categories
394
397
 
395
- def execute(self, query: str, *params, commitable: bool = False) -> Optional[sqlite3.Cursor]:
398
+ def execute(self, query: str, *params, commitable: bool = False) -> sqlite3.Cursor:
396
399
  """ helper method that performs retriable queries and marks the transaction as needing to commit
397
400
  if the query is commitable."""
398
401
  if commitable:
@@ -404,14 +407,14 @@ class ImportPlugin(plugins.ImportPluginBase):
404
407
  except sqlite3.OperationalError as ex:
405
408
  if "no transaction is active" in str(ex):
406
409
  self.print(f"no transaction for {query}")
407
- return
410
+ raise
408
411
  if not attempts:
409
412
  raise
410
413
  attempts -= 1
411
414
  self.print(f'Retrying query \'{query}\': {ex!s}')
412
415
  time.sleep(1)
413
416
 
414
- def executemany(self, query: str, data: Iterable[Any], *, commitable: bool = False) -> Optional[sqlite3.Cursor]:
417
+ def executemany(self, query: str, data: Iterable[Any], *, commitable: bool = False) -> sqlite3.Cursor:
415
418
  """ helper method that performs retriable queries and marks the transaction as needing to commit
416
419
  if the query is commitable."""
417
420
  if commitable:
@@ -423,7 +426,7 @@ class ImportPlugin(plugins.ImportPluginBase):
423
426
  except sqlite3.OperationalError as ex:
424
427
  if "no transaction is active" in str(ex):
425
428
  self.print(f"no transaction for {query}")
426
- return
429
+ raise
427
430
  if not attempts:
428
431
  raise
429
432
  attempts -= 1
tradedangerous/version.py CHANGED
@@ -12,5 +12,5 @@
12
12
  """just keeper of current version"""
13
13
 
14
14
  # TODO: remember to update tests when version changes
15
- __version__ = '11.0.3'
15
+ __version__ = '11.0.4'
16
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tradedangerous
3
- Version: 11.0.3
3
+ Version: 11.0.4
4
4
  Summary: Trade-Dangerous is a set of powerful trading tools for Elite Dangerous, organized around one of the most powerful trade run optimizers available.
5
5
  Home-page: https://github.com/eyeonus/Trade-Dangerous
6
6
  Author: eyeonus
@@ -1,11 +1,11 @@
1
1
  tradedangerous/__init__.py,sha256=bwsbE_GyCNsuyGDKnfXAg0RD-ewsWHliySJ5QfCK7h8,1166
2
- tradedangerous/cache.py,sha256=VH7_Bs-vz1H9KEbQah_4w3zqY3nG7HOegHZGzlKip80,36028
3
- tradedangerous/cli.py,sha256=4B6FFYyH8M6Z4D8CJHsrkABHsb_O7pmTrJ7M_lB2Za8,4531
2
+ tradedangerous/cache.py,sha256=mROADjtCtSWkvR6crdl7SycBH0AjuaceagUj-x9RZDM,36071
3
+ tradedangerous/cli.py,sha256=dLekZ3MTbn9XcSGtE532qZF3iSnsb5G-ddQyErwTv9o,4559
4
4
  tradedangerous/corrections.py,sha256=_WLgo1IBWoskrrPFeshRwCOeJ2BeJb_x4tDQ0JdAo-s,1340
5
- tradedangerous/csvexport.py,sha256=0XrQKLdt1STinZsBlBN3NHGh0BT81HKVe_oi9lFlLno,8637
5
+ tradedangerous/csvexport.py,sha256=_19bGVnCGJPzvx_8DzLGOs4rqx8Asn7eCVdXKdKu92E,8653
6
6
  tradedangerous/edscupdate.py,sha256=G8N-j61MZbCgY07WIVK23CCw6eMPE3WkraXHeARt9QE,17297
7
7
  tradedangerous/edsmupdate.py,sha256=jpDUxUkDKxlG_gS3qWy5fg9p3mBPC-akU2pHSlfTPkI,14915
8
- tradedangerous/formatting.py,sha256=0NbDTQ5fV8x3lhmPTtpnAwQog79dCjPCbDNU5b-Vn6U,6896
8
+ tradedangerous/formatting.py,sha256=-FdlH1yMKPkqhpCtKmbl0chbyZ1VjKZsZ8OLc8v-Knc,6927
9
9
  tradedangerous/fs.py,sha256=_5OJHIRy_M-WFHEZO4g2EIgqDxdrz4s8tcdDmgG7NME,2494
10
10
  tradedangerous/gui.py,sha256=DFsF5zATr-lyJShL6t5kPKvcLLJYkICurzBz0WBa-oQ,43676
11
11
  tradedangerous/jsonprices.py,sha256=GAaNbfEs1LX_F5f69MiEhHIEwoGZtubGC8EQ9MM4B9A,6878
@@ -21,7 +21,7 @@ tradedangerous/tradeexcept.py,sha256=aZ-Y31MbkjF7lmAzBAbaMsPPE7FEEfuf4gaX2GvriDk
21
21
  tradedangerous/tradegui.py,sha256=q2HdIdoyeLUpeF2X0hVIGn7sU6T4zOzq1HN0zGvZdyE,788
22
22
  tradedangerous/transfers.py,sha256=88gIvXpjd8T6NLxBrBRzmH2IfUmDDtiMcDTrc7qF3OI,7830
23
23
  tradedangerous/utils.py,sha256=PUPvAEqUyxYGqqQa0b_yfLAvq8YVUxK6HfdS-CxM-Lo,5186
24
- tradedangerous/version.py,sha256=vmpGvM433TmKuZp1iADOkzMDv717JW7vbKl2i__IgFg,646
24
+ tradedangerous/version.py,sha256=VIRNV1KJN_g_ByHP19vQGbhe4W2DtWVHoyZrEjvMkaI,646
25
25
  tradedangerous/commands/TEMPLATE.py,sha256=MOE69xsZPHPIMBQ-LXicfsOlCZdy-2gPX_nlnwYYil8,2026
26
26
  tradedangerous/commands/__init__.py,sha256=3gz2cnXNZNkV1gtZh0dOnCRxBkQHbeIyysRe3bM2WEE,9516
27
27
  tradedangerous/commands/buildcache_cmd.py,sha256=_8vKu9e3tQy0HEPrnG8Ts0OoQ_kF6gZPQ9EOfTUd73w,2179
@@ -62,18 +62,18 @@ tradedangerous/misc/progress.py,sha256=NKvKP1OSCTpItc1CNxDuEH2A1oGJ6aWSyCdPSAjsG
62
62
  tradedangerous/plugins/__init__.py,sha256=TL-OIptlqNENKhoFqkFeBJn_vSw8L0pVaDJgjhaTj7A,7860
63
63
  tradedangerous/plugins/edapi_plug.py,sha256=5nqBYmjUceAt-KTfiBn7IEl443R1SsGLDmfVXgbcyms,42262
64
64
  tradedangerous/plugins/edcd_plug.py,sha256=JuDtuEM_mN9Sz2H09-qYizM-9N3cuNjgvQy7Y-wHwKw,14412
65
- tradedangerous/plugins/eddblink_plug.py,sha256=pfPROnJKhvLHDNruOx_wHGK876aYS7dE8xcMVuWw1ak,21961
65
+ tradedangerous/plugins/eddblink_plug.py,sha256=EWJrVn5twwPVT-vGicmWuL4NAwf69G_0mw9lnjzn1Xw,21606
66
66
  tradedangerous/plugins/edmc_batch_plug.py,sha256=rrP_lFFxWsba8DPEo0WF2EdCiMoRC7tCT8z62MIvtIo,4173
67
67
  tradedangerous/plugins/journal_plug.py,sha256=5HMyoxQ7z42qj7NiL8rDxSyTN9gKikoQjyWzJLD-SYQ,23746
68
68
  tradedangerous/plugins/netlog_plug.py,sha256=yUl47l9xt3kGj9oSiY_FZaDGdnQj63oa9MBtSeIy1Zo,13469
69
- tradedangerous/plugins/spansh_plug.py,sha256=viIlFNrVocRKjZ1rg8mNTZsHaZoSAn5_6vEGn67suVQ,26730
69
+ tradedangerous/plugins/spansh_plug.py,sha256=FmOJcYq6-ENYdVNRN96kYdskeqZ4IqzjEsyCYm6hVXI,26762
70
70
  tradedangerous/templates/Added.csv,sha256=8o54civQCcS9y7_DBo0GX196XWRbbREQqKDYTKibsgQ,649
71
71
  tradedangerous/templates/Category.csv,sha256=8xwUDcBZE25T6x6dZGlRUMTCqeDLt3a9LXU5h6hRHV8,250
72
72
  tradedangerous/templates/RareItem.csv,sha256=F1RhRnTD82PiwrVUO-ai2ErGH2PTqNnQaDw5mcgljXs,10483
73
73
  tradedangerous/templates/TradeDangerous.sql,sha256=VlQK7QGtEi2brGtWaIZDvKmbJ_vLocD4CJ8h_6kKptU,7808
74
- tradedangerous-11.0.3.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
75
- tradedangerous-11.0.3.dist-info/METADATA,sha256=gpeUG3Jsq6EG53G-TYq4jBflc8husLo2t5ICNoKFXSk,4435
76
- tradedangerous-11.0.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
77
- tradedangerous-11.0.3.dist-info/entry_points.txt,sha256=pSwa-q0ob443uiKux7xFKYQl8uen66iDTnjdrQhNLx8,92
78
- tradedangerous-11.0.3.dist-info/top_level.txt,sha256=bF29i-oEltmNICgElEKxNsg83oahJvxg3a7YrxZi9Rk,15
79
- tradedangerous-11.0.3.dist-info/RECORD,,
74
+ tradedangerous-11.0.4.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
75
+ tradedangerous-11.0.4.dist-info/METADATA,sha256=wtvXMMQTXzopIxDvS5GcaFubPfU2pDHlM3T6csHiTlY,4435
76
+ tradedangerous-11.0.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
77
+ tradedangerous-11.0.4.dist-info/entry_points.txt,sha256=pSwa-q0ob443uiKux7xFKYQl8uen66iDTnjdrQhNLx8,92
78
+ tradedangerous-11.0.4.dist-info/top_level.txt,sha256=bF29i-oEltmNICgElEKxNsg83oahJvxg3a7YrxZi9Rk,15
79
+ tradedangerous-11.0.4.dist-info/RECORD,,