tradedangerous 10.13.10__py3-none-any.whl → 10.14.2__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
@@ -285,12 +285,12 @@ def getStationByNameIndex(cur):
285
285
  """ Build station index in STAR/Station notation """
286
286
  cur.execute("""
287
287
  SELECT station_id,
288
- UPPER(system.name) || '/' || UPPER(station.name)
288
+ system.name || '/' || station.name
289
289
  FROM System
290
290
  INNER JOIN Station
291
291
  USING (system_id)
292
292
  """)
293
- return { name: ID for (ID, name) in cur }
293
+ return { name.upper(): ID for (ID, name) in cur }
294
294
 
295
295
 
296
296
  def getItemByNameIndex(cur):
@@ -0,0 +1,379 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ from datetime import datetime, timedelta
5
+ from collections import namedtuple
6
+ from pathlib import Path
7
+
8
+ import requests
9
+ import simdjson
10
+ import sqlite3
11
+
12
+ from .. import plugins
13
+
14
+ SOURCE_URL = 'https://downloads.spansh.co.uk/galaxy_stations.json'
15
+
16
+ STATION_TYPE_MAP = {
17
+ 'Drake-Class Carrier': 24, # fleet carriers
18
+ 'Settlement': 25, # odyssey settlements
19
+ }
20
+
21
+ System = namedtuple('System', 'name,pos_x,pos_y,pos_z,modified')
22
+ Station = namedtuple('Station', 'name,distance,max_pad_size,market,black_market,shipyard,outfitting,rearm,refuel,repair,planetary,type,modified')
23
+ Commodity = namedtuple('Commodity', 'id,name,category,demand,supply,sell,buy,modified')
24
+
25
+
26
+ class Timing:
27
+
28
+ def __init__(self):
29
+ self.start_ts = None
30
+ self.end_ts = None
31
+
32
+ def __enter__(self):
33
+ self.start_ts = time.perf_counter()
34
+ self.end_ts = None
35
+ return self
36
+
37
+ def __exit__(self, *args):
38
+ self.end_ts = time.perf_counter()
39
+
40
+ @property
41
+ def elapsed(self):
42
+ if self.start_ts is None:
43
+ return None
44
+ return (self.end_ts or time.perf_counter()) - self.start_ts
45
+
46
+ @property
47
+ def is_finished(self):
48
+ return self.end_ts is not None
49
+
50
+
51
+ class ImportPlugin(plugins.ImportPluginBase):
52
+ """Plugin that downloads data from https://spansh.co.uk/dumps.
53
+ """
54
+
55
+ pluginOptions = {
56
+ 'url': f'URL to download galaxy data from (defaults to {SOURCE_URL})',
57
+ 'file': 'Local filename to import galaxy data from; use "-" to load from stdin',
58
+ }
59
+
60
+ def __init__(self, *args, **kwargs):
61
+ super().__init__(*args, **kwargs)
62
+ self.url = self.getOption('url')
63
+ self.file = self.getOption('file')
64
+ assert not (self.url and self.file), 'Provide either url or file, not both'
65
+ if self.file and (self.file != '-'):
66
+ self.file = (Path(self.tdenv.cwDir) / self.file).resolve()
67
+ self.known_space = self.load_known_space()
68
+ self.known_commodities = self.load_known_commodities()
69
+
70
+ def print(self, *args, **kwargs):
71
+ return self.tdenv.uprint(*args, **kwargs)
72
+
73
+ def run(self):
74
+ self.tdenv.filename = str(self.tdb.pricesPath)
75
+ if self.tdenv.detail < 1:
76
+ self.print('This will take at least several minutes...')
77
+ self.print('You can increase verbosity (-v) to get a sense of progress')
78
+ with open(self.tdb.pricesPath, 'w') as f, Timing() as timing:
79
+ self.print(f'Writing prices to {self.tdb.pricesPath}')
80
+ f.write('# Generated from spansh galaxy data\n')
81
+ f.write(f'# Source: {self.file or self.url}\n')
82
+ f.write('#\n')
83
+ f.write((
84
+ '# {name:50s} {sell:>7s} {buy:>7s} '
85
+ '{demand:>11s} {supply:>11s} {ts}\n'
86
+ ).format(
87
+ name='Item Name',
88
+ sell='SellCr',
89
+ buy='BuyCr',
90
+ demand='Demand',
91
+ supply='Supply',
92
+ ts='Timestamp',
93
+ ))
94
+ system_count = 0
95
+ total_station_count = 0
96
+ total_commodity_count = 0
97
+ self.need_commit = False
98
+ seen_stations = set()
99
+ for system, stations in self.data_stream():
100
+ self.ensure_system(system)
101
+ station_count = 0
102
+ commodity_count = 0
103
+ for station, commodities in stations:
104
+ if (system.name.upper(), station.name.upper()) in seen_stations:
105
+ fq_station_name = f'@{system.name.upper()}/{station.name}'
106
+ if self.tdenv.detail >= 1:
107
+ self.print(f' | {fq_station_name:50s} | Skipping duplicate station record')
108
+ continue
109
+ seen_stations.add((system.name.upper(), station.name.upper()))
110
+ self.ensure_station(system, station)
111
+ f.write('\n')
112
+ f.write(f'@ {system.name.upper()}/{station.name}\n')
113
+ categories = self.categorise_commodities(commodities)
114
+ for category_name, category_commodities in categories.items():
115
+ f.write(f' + {category_name}\n')
116
+ for commodity in category_commodities:
117
+ commodity = self.ensure_commodity(station, commodity)
118
+ f.write((
119
+ ' {name:50s} {sell:7d} {buy:7d} '
120
+ '{demand:10d}? {supply:10d}? {modified}\n'
121
+ ).format(
122
+ name=commodity.name,
123
+ sell=commodity.sell,
124
+ buy=commodity.buy,
125
+ demand=commodity.demand,
126
+ supply=commodity.supply,
127
+ modified=commodity.modified,
128
+ ))
129
+ commodity_count += 1
130
+ station_count += 1
131
+ system_count += 1
132
+ total_station_count += station_count
133
+ total_commodity_count += commodity_count
134
+ if self.tdenv.detail >= 1:
135
+ self.print(
136
+ f'{system_count:6d} | {system.name.upper():50s} | '
137
+ f'{station_count:3d} st {commodity_count:6d} co'
138
+ )
139
+ if self.need_commit:
140
+ self.execute('COMMIT')
141
+ self.need_commit = False
142
+ self.print(
143
+ f'{timedelta(seconds=int(timing.elapsed))!s} Done '
144
+ f'{total_station_count} st {total_commodity_count} co'
145
+ )
146
+ return True
147
+
148
+ def data_stream(self):
149
+ if self.file == '-':
150
+ self.print('Reading prices from stdin')
151
+ stream = sys.stdin
152
+ elif self.file:
153
+ self.print(f'Reading prices from local file: {self.file}')
154
+ stream = open(self.file, 'r', encoding='utf8')
155
+ else:
156
+ url = self.url or SOURCE_URL
157
+ self.print(f'Reading prices from remote URL: {url}')
158
+ req = requests.get(url, stream=True)
159
+ stream = req.iter_lines(decode_unicode=True)
160
+ return ingest_stream(stream)
161
+
162
+ def categorise_commodities(self, commodities):
163
+ categories = {}
164
+ for commodity in commodities:
165
+ categories.setdefault(commodity.category, []).append(commodity)
166
+ return categories
167
+
168
+ def execute(self, query, *params, **kwparams):
169
+ attempts = 5
170
+ cursor = self.tdb.getDB().cursor()
171
+ while True:
172
+ try:
173
+ return cursor.execute(query, params or kwparams)
174
+ except sqlite3.OperationalError as ex:
175
+ if not attempts:
176
+ raise
177
+ attempts -= 1
178
+ self.print(f'Retrying query: {ex!s}')
179
+ time.sleep(1)
180
+
181
+ def load_known_space(self):
182
+ cache = {}
183
+ result = self.execute(
184
+ '''
185
+ SELECT System.name, Station.name FROM System
186
+ LEFT JOIN Station USING (system_id)
187
+ '''
188
+ ).fetchall()
189
+ for system, station in result:
190
+ cache.setdefault(system.upper(), set())
191
+ if station is not None:
192
+ cache[system.upper()].add(station.upper())
193
+ return cache
194
+
195
+ def load_known_commodities(self):
196
+ return dict(self.execute('SELECT fdev_id, name FROM Item').fetchall())
197
+
198
+ def ensure_system(self, system):
199
+ if system.name.upper() in self.known_space:
200
+ return
201
+ self.execute(
202
+ '''
203
+ INSERT INTO System (name, pos_x, pos_y, pos_z, modified) VALUES (?, ?, ?, ?, ?)
204
+ ''',
205
+ system.name, system.pos_x, system.pos_y, system.pos_z, system.modified,
206
+ )
207
+ self.need_commit = True
208
+ if self.tdenv.detail >= 2:
209
+ self.print(f' | {system.name.upper():50s} | Added missing system')
210
+ self.known_space[system.name.upper()] = set()
211
+
212
+ def ensure_station(self, system, station):
213
+ if station.name.upper() in self.known_space.get(system.name.upper(), set()):
214
+ return
215
+ self.execute(
216
+ '''
217
+ INSERT INTO Station (
218
+ system_id,
219
+ name,
220
+ ls_from_star,
221
+ max_pad_size,
222
+ market,
223
+ blackmarket,
224
+ shipyard,
225
+ outfitting,
226
+ rearm,
227
+ refuel,
228
+ repair,
229
+ planetary,
230
+ modified,
231
+ type_id
232
+ )
233
+ VALUES (
234
+ (SELECT system_id FROM System WHERE upper(name) = ?),
235
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
236
+ )
237
+ ''',
238
+ system.name.upper(),
239
+ station.name,
240
+ station.distance,
241
+ station.max_pad_size,
242
+ self.bool_yn(station.market),
243
+ self.bool_yn(station.black_market),
244
+ self.bool_yn(station.shipyard),
245
+ self.bool_yn(station.outfitting),
246
+ self.bool_yn(station.rearm),
247
+ self.bool_yn(station.refuel),
248
+ self.bool_yn(station.repair),
249
+ self.bool_yn(station.planetary),
250
+ station.modified,
251
+ station.type,
252
+ )
253
+ self.need_commit = True
254
+ if self.tdenv.detail >= 2:
255
+ self.print(f' | {station.name:50s} | Added missing station')
256
+ self.known_space[system.name.upper()].add(station.name.upper())
257
+
258
+ def ensure_commodity(self, station, commodity):
259
+ if commodity.id in self.known_commodities:
260
+ if self.known_commodities[commodity.id] != commodity.name:
261
+ if self.tdenv.detail >= 3:
262
+ self.print(f' | - {station.name:45s} | Replace "{commodity.name}" with pre-existing "{self.known_commodities[commodity.id]}"')
263
+ return Commodity(
264
+ id=commodity.id,
265
+ name=self.known_commodities[commodity.id],
266
+ category=commodity.category,
267
+ demand=commodity.demand,
268
+ supply=commodity.supply,
269
+ sell=commodity.sell,
270
+ buy=commodity.buy,
271
+ modified=commodity.modified,
272
+ )
273
+ return commodity
274
+ self.execute(
275
+ '''
276
+ INSERT INTO Item (category_id, name, fdev_id)
277
+ VALUES ((SELECT category_id FROM Category WHERE upper(name) = ?), ?, ?)
278
+ ''',
279
+ commodity.category.upper(),
280
+ commodity.name,
281
+ commodity.id,
282
+ )
283
+ self.need_commit = True
284
+ if self.tdenv.detail >= 2:
285
+ self.print(f' | {commodity.name:50s} | Added missing commodity')
286
+ self.known_commodities[commodity.id] = commodity.name
287
+ return commodity
288
+
289
+ def bool_yn(self, value):
290
+ return '?' if value is None else ('Y' if value else 'N')
291
+
292
+
293
+ def ingest_stream(stream):
294
+ """Ingest a spansh-style galaxy dump, yielding system-level data."""
295
+ line = next(stream)
296
+ assert line.rstrip(' \n,') == '['
297
+ for line in stream:
298
+ line = line.rstrip().rstrip(',')
299
+ if line == ']':
300
+ break
301
+ system_data = simdjson.Parser().parse(line)
302
+ coords = system_data.get('coords', {})
303
+ yield (
304
+ System(
305
+ name=system_data.get('name', 'Unnamed'),
306
+ pos_x=coords.get('x', 999999),
307
+ pos_y=coords.get('y', 999999),
308
+ pos_z=coords.get('z', 999999),
309
+ modified=parse_ts(system_data.get('date')),
310
+ ),
311
+ ingest_stations(system_data),
312
+ )
313
+
314
+
315
+ def ingest_stations(system_data):
316
+ """Ingest system-level data, yielding station-level data."""
317
+ targets = [system_data, *system_data.get('bodies', ())]
318
+ is_planetary = False
319
+ for target in targets:
320
+ for station_data in target.get('stations', ()):
321
+ services = set(station_data.get('services', ()))
322
+ if 'Market' not in services:
323
+ continue
324
+ market = station_data.get('market', {})
325
+ if not market.get('commodities'):
326
+ continue
327
+ landing_pads = station_data.get('landingPads', {})
328
+ max_pad_size = '?'
329
+ if landing_pads.get('large'):
330
+ max_pad_size = 'L'
331
+ elif landing_pads.get('medium'):
332
+ max_pad_size = 'M'
333
+ elif landing_pads.get('small'):
334
+ max_pad_size = 'S'
335
+ yield (
336
+ Station(
337
+ name=station_data.get('name', 'Unnamed'),
338
+ distance=station_data.get('distanceToArrival', 999999),
339
+ max_pad_size=max_pad_size,
340
+ market=True,
341
+ black_market='Black Market' in services,
342
+ shipyard='Shipyard' in services,
343
+ outfitting='Outfitting' in services,
344
+ rearm='Restock' in services,
345
+ refuel='Refuel' in services,
346
+ repair='Repair' in services,
347
+ planetary=is_planetary,
348
+ type=STATION_TYPE_MAP.get(station_data.get('type'), 0),
349
+ modified=parse_ts(station_data.get('updateTime')),
350
+ ),
351
+ ingest_market(market),
352
+ )
353
+ # first target is system stations, everything else is planetary
354
+ is_planetary = True
355
+
356
+
357
+ def ingest_market(market):
358
+ """Ingest station-level market data, yielding commodities."""
359
+ for commodity in market['commodities']:
360
+ yield Commodity(
361
+ id=commodity.get('commodityId'),
362
+ name=commodity.get('name', 'Unnamed'),
363
+ category=commodity.get('category', 'Uncategorised'),
364
+ demand=commodity.get('demand', 0),
365
+ supply=commodity.get('supply', 0),
366
+ sell=commodity.get('sellPrice', 0),
367
+ buy=commodity.get('buyPrice', 0),
368
+ modified=parse_ts(market.get('updateTime'))
369
+ )
370
+
371
+
372
+ def parse_ts(ts):
373
+ if ts is None:
374
+ return None
375
+ if ts.endswith('+00'):
376
+ ts = ts[:-3]
377
+ if '.' not in ts:
378
+ ts += '.0'
379
+ return datetime.strptime(ts, '%Y-%m-%d %H:%M:%S.%f').replace(microsecond=0)
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__ = '10.13.10'
15
+ __version__ = '10.14.2'
16
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tradedangerous
3
- Version: 10.13.10
3
+ Version: 10.14.2
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
@@ -21,6 +21,7 @@ Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: requests
23
23
  Requires-Dist: appJar
24
+ Requires-Dist: pysimdjson
24
25
 
25
26
 
26
27
  ----------
@@ -1,5 +1,5 @@
1
1
  tradedangerous/__init__.py,sha256=5ZeypoZaM8hlh6c-yTkD8x5hZYP8q3Q8a3bVeicHr90,1122
2
- tradedangerous/cache.py,sha256=_kcGrEI1CMbMftJvGU3QpXIUPwEj_2-BcL-WHmGGHWA,32948
2
+ tradedangerous/cache.py,sha256=t9--q6etNH5vigvA_1VLf_XTdoC_116qkjnqswhshpg,32942
3
3
  tradedangerous/cli.py,sha256=ycGkzMszwpP9OrngFIyhQbYoZ2wzRhJMTDCW1YhLrNI,4727
4
4
  tradedangerous/corrections.py,sha256=QMs-7MKLw2imFgIHthnwcpqWT1yJTb3CrABJw9LaKLA,1441
5
5
  tradedangerous/csvexport.py,sha256=OcOKe_VIgafw6rCvG3k5tM11KwwoYXMNY5DSSfCC0mU,8705
@@ -21,7 +21,7 @@ tradedangerous/tradeexcept.py,sha256=aZ-Y31MbkjF7lmAzBAbaMsPPE7FEEfuf4gaX2GvriDk
21
21
  tradedangerous/tradegui.py,sha256=JbGFnsWupgesk6hrcUgKSdD9NNDyo0U9gh6m3DccAwU,782
22
22
  tradedangerous/transfers.py,sha256=NmXXk2aF88YkAvYqc9Syt_aO6d2jJjC-OxoRFoOyQH4,9923
23
23
  tradedangerous/utils.py,sha256=PUPvAEqUyxYGqqQa0b_yfLAvq8YVUxK6HfdS-CxM-Lo,5186
24
- tradedangerous/version.py,sha256=XS35iBSFdbTM6DjV7JPdxmKUR629HgyX_cKgx8qIsaM,648
24
+ tradedangerous/version.py,sha256=5iuvqnu0uphkiczIVtwP6oY8olhV7oHQ5oaAUqtQt6g,647
25
25
  tradedangerous/commands/TEMPLATE.py,sha256=7oXL124aqxGHwnb0h9yRylUiwc6M5QrRrGVrubwI1gg,2124
26
26
  tradedangerous/commands/__init__.py,sha256=6B0WuqkFBOll5Hj67yKDAnhmyr5ZAnHc6nzUNEUh384,9640
27
27
  tradedangerous/commands/buildcache_cmd.py,sha256=oJvP06fA8svnHrfrpWkHKR16cba8GIhHdMOyZqds18Y,2332
@@ -66,13 +66,14 @@ tradedangerous/plugins/eddblink_plug.py,sha256=n8AzLh2pJdHdar1SnqSSjCwWPl4f-OKQ8
66
66
  tradedangerous/plugins/edmc_batch_plug.py,sha256=3Ptr-SZqaZFR8ViIIrp9Ak7rvfU3zl11AZYBhIceN7s,4224
67
67
  tradedangerous/plugins/journal_plug.py,sha256=K1oIeI7E3mb04fvYLXyoAh7fOTyM9NBelibTI88MIDQ,23696
68
68
  tradedangerous/plugins/netlog_plug.py,sha256=Gw_HSZWpN17D--OIYEM3Vo8y9SvDOv9UwAUfY24kz28,13460
69
+ tradedangerous/plugins/spansh_plug.py,sha256=ImU1tp995LhzFX7sJXApLiwrxx29b2WXR0jtWvm5O0U,14485
69
70
  tradedangerous/templates/Added.csv,sha256=8o54civQCcS9y7_DBo0GX196XWRbbREQqKDYTKibsgQ,649
70
71
  tradedangerous/templates/DefaultShipIndex.json,sha256=m5cI3vkKiqRk1VKO1Z_8LZrG9nczV0PUMDfBSt4-1RM,94739
71
72
  tradedangerous/templates/RareItem.csv,sha256=F1RhRnTD82PiwrVUO-ai2ErGH2PTqNnQaDw5mcgljXs,10483
72
73
  tradedangerous/templates/TradeDangerous.sql,sha256=6sjEogGHy-9zYpjPo0Y2a5tElowmHFyJNwrimOUBfHk,8079
73
- tradedangerous-10.13.10.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
74
- tradedangerous-10.13.10.dist-info/METADATA,sha256=Aq7LfqX_C0zTErgC2jOcfVSem8ZJJhZ1vBW0q9ajo1M,4396
75
- tradedangerous-10.13.10.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
76
- tradedangerous-10.13.10.dist-info/entry_points.txt,sha256=pSwa-q0ob443uiKux7xFKYQl8uen66iDTnjdrQhNLx8,92
77
- tradedangerous-10.13.10.dist-info/top_level.txt,sha256=bF29i-oEltmNICgElEKxNsg83oahJvxg3a7YrxZi9Rk,15
78
- tradedangerous-10.13.10.dist-info/RECORD,,
74
+ tradedangerous-10.14.2.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
75
+ tradedangerous-10.14.2.dist-info/METADATA,sha256=F90OuVRZ5eI8SzlzQDdmhrrOQQbEa0RnAMlRKXX-KBk,4421
76
+ tradedangerous-10.14.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
77
+ tradedangerous-10.14.2.dist-info/entry_points.txt,sha256=pSwa-q0ob443uiKux7xFKYQl8uen66iDTnjdrQhNLx8,92
78
+ tradedangerous-10.14.2.dist-info/top_level.txt,sha256=bF29i-oEltmNICgElEKxNsg83oahJvxg3a7YrxZi9Rk,15
79
+ tradedangerous-10.14.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.38.4)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5