tradedangerous 12.7.6__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.
- py.typed +1 -0
- trade.py +49 -0
- tradedangerous/__init__.py +43 -0
- tradedangerous/cache.py +1381 -0
- tradedangerous/cli.py +136 -0
- tradedangerous/commands/TEMPLATE.py +74 -0
- tradedangerous/commands/__init__.py +244 -0
- tradedangerous/commands/buildcache_cmd.py +102 -0
- tradedangerous/commands/buy_cmd.py +427 -0
- tradedangerous/commands/commandenv.py +372 -0
- tradedangerous/commands/exceptions.py +94 -0
- tradedangerous/commands/export_cmd.py +150 -0
- tradedangerous/commands/import_cmd.py +222 -0
- tradedangerous/commands/local_cmd.py +243 -0
- tradedangerous/commands/market_cmd.py +207 -0
- tradedangerous/commands/nav_cmd.py +252 -0
- tradedangerous/commands/olddata_cmd.py +270 -0
- tradedangerous/commands/parsing.py +221 -0
- tradedangerous/commands/rares_cmd.py +298 -0
- tradedangerous/commands/run_cmd.py +1521 -0
- tradedangerous/commands/sell_cmd.py +262 -0
- tradedangerous/commands/shipvendor_cmd.py +60 -0
- tradedangerous/commands/station_cmd.py +68 -0
- tradedangerous/commands/trade_cmd.py +181 -0
- tradedangerous/commands/update_cmd.py +67 -0
- tradedangerous/corrections.py +55 -0
- tradedangerous/csvexport.py +234 -0
- tradedangerous/db/__init__.py +27 -0
- tradedangerous/db/adapter.py +192 -0
- tradedangerous/db/config.py +107 -0
- tradedangerous/db/engine.py +259 -0
- tradedangerous/db/lifecycle.py +332 -0
- tradedangerous/db/locks.py +208 -0
- tradedangerous/db/orm_models.py +500 -0
- tradedangerous/db/paths.py +113 -0
- tradedangerous/db/utils.py +661 -0
- tradedangerous/edscupdate.py +565 -0
- tradedangerous/edsmupdate.py +474 -0
- tradedangerous/formatting.py +210 -0
- tradedangerous/fs.py +156 -0
- tradedangerous/gui.py +1146 -0
- tradedangerous/mapping.py +133 -0
- tradedangerous/mfd/__init__.py +103 -0
- tradedangerous/mfd/saitek/__init__.py +3 -0
- tradedangerous/mfd/saitek/directoutput.py +678 -0
- tradedangerous/mfd/saitek/x52pro.py +195 -0
- tradedangerous/misc/checkpricebounds.py +287 -0
- tradedangerous/misc/clipboard.py +49 -0
- tradedangerous/misc/coord64.py +83 -0
- tradedangerous/misc/csvdialect.py +57 -0
- tradedangerous/misc/derp-sentinel.py +35 -0
- tradedangerous/misc/diff-system-csvs.py +159 -0
- tradedangerous/misc/eddb.py +81 -0
- tradedangerous/misc/eddn.py +349 -0
- tradedangerous/misc/edsc.py +437 -0
- tradedangerous/misc/edsm.py +121 -0
- tradedangerous/misc/importeddbstats.py +54 -0
- tradedangerous/misc/prices-json-exp.py +179 -0
- tradedangerous/misc/progress.py +194 -0
- tradedangerous/plugins/__init__.py +249 -0
- tradedangerous/plugins/edcd_plug.py +371 -0
- tradedangerous/plugins/eddblink_plug.py +861 -0
- tradedangerous/plugins/edmc_batch_plug.py +133 -0
- tradedangerous/plugins/spansh_plug.py +2647 -0
- tradedangerous/prices.py +211 -0
- tradedangerous/submit-distances.py +422 -0
- tradedangerous/templates/Added.csv +37 -0
- tradedangerous/templates/Category.csv +17 -0
- tradedangerous/templates/RareItem.csv +143 -0
- tradedangerous/templates/TradeDangerous.sql +338 -0
- tradedangerous/tools.py +40 -0
- tradedangerous/tradecalc.py +1302 -0
- tradedangerous/tradedb.py +2320 -0
- tradedangerous/tradeenv.py +313 -0
- tradedangerous/tradeenv.pyi +109 -0
- tradedangerous/tradeexcept.py +131 -0
- tradedangerous/tradeorm.py +183 -0
- tradedangerous/transfers.py +192 -0
- tradedangerous/utils.py +243 -0
- tradedangerous/version.py +16 -0
- tradedangerous-12.7.6.dist-info/METADATA +106 -0
- tradedangerous-12.7.6.dist-info/RECORD +87 -0
- tradedangerous-12.7.6.dist-info/WHEEL +5 -0
- tradedangerous-12.7.6.dist-info/entry_points.txt +3 -0
- tradedangerous-12.7.6.dist-info/licenses/LICENSE +373 -0
- tradedangerous-12.7.6.dist-info/top_level.txt +2 -0
- tradegui.py +24 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tradeorm provides the TradeORM class which uses the application database
|
|
3
|
+
rather than trying to be its own database in its own right like TradeDB.
|
|
4
|
+
|
|
5
|
+
Suggested use:
|
|
6
|
+
|
|
7
|
+
# TradeEnv is optional, it's for controlling environment settings
|
|
8
|
+
# builder-pattern style.
|
|
9
|
+
from tradedangerous import TradeEnv, TradeORM
|
|
10
|
+
|
|
11
|
+
tde = TradeEnv() # debug settings, color, etc...
|
|
12
|
+
tdo = TradeORM(tde) # if not supplied, it will make its own
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import os
|
|
17
|
+
import typing
|
|
18
|
+
|
|
19
|
+
from . import TradeEnv
|
|
20
|
+
from .tradeexcept import AmbiguityError, TradeException, MissingDB, SystemNotStationError
|
|
21
|
+
from .db import (
|
|
22
|
+
orm_models as orm, # type: ignore # so we can access models easily
|
|
23
|
+
make_engine_from_config, # type: ignore
|
|
24
|
+
get_session_factory, # type: ignore
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if typing.TYPE_CHECKING:
|
|
28
|
+
from .db.engine import sessionmaker, Engine, Session # type: ignore
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TradeORM:
|
|
32
|
+
DEFAULT_PATH = "data"
|
|
33
|
+
DEFAULT_DB = "TradeDangerous.db"
|
|
34
|
+
DB_CONFIG_VAR = "TD_DB_CONFIG"
|
|
35
|
+
DB_CONFIG_FILE = "db_config.ini"
|
|
36
|
+
|
|
37
|
+
data_dir: Path
|
|
38
|
+
db_path: Path
|
|
39
|
+
|
|
40
|
+
engine: Engine
|
|
41
|
+
session_maker: sessionmaker[Session]
|
|
42
|
+
session: Session
|
|
43
|
+
|
|
44
|
+
def __init__(self, *, tdenv: TradeEnv | None = None, debug: int | None = None):
|
|
45
|
+
tdenv = tdenv or TradeEnv(debug=debug or 0)
|
|
46
|
+
self.tdenv = tdenv
|
|
47
|
+
|
|
48
|
+
# Determine where the database should be
|
|
49
|
+
data_dir = tdenv.dataDir or TradeORM.DEFAULT_PATH
|
|
50
|
+
self.data_dir = Path(data_dir)
|
|
51
|
+
tdenv.DEBUG0("data_dir = {}", self.data_dir)
|
|
52
|
+
|
|
53
|
+
# Determine the path to the file itself
|
|
54
|
+
db_path = tdenv.dbFilename or (self.data_dir / TradeORM.DEFAULT_DB)
|
|
55
|
+
self.db_path = Path(db_path)
|
|
56
|
+
tdenv.DEBUG0("db_path = {}", self.db_path)
|
|
57
|
+
|
|
58
|
+
# We need it to exist.
|
|
59
|
+
if not self.db_path.exists():
|
|
60
|
+
raise MissingDB(self.db_path)
|
|
61
|
+
|
|
62
|
+
default_config = self.data_dir / TradeORM.DB_CONFIG_FILE
|
|
63
|
+
db_config = os.environ.get(TradeORM.DB_CONFIG_VAR, default_config)
|
|
64
|
+
tdenv.DEBUG0("db_config = {}", db_config)
|
|
65
|
+
|
|
66
|
+
# Make the database available.
|
|
67
|
+
self.engine = make_engine_from_config(db_config)
|
|
68
|
+
|
|
69
|
+
# The user will expect objects (instances of models) that we return
|
|
70
|
+
# to have the same lifetime as the TradeORM() instance, so we want
|
|
71
|
+
# a main session for things to use and return from.
|
|
72
|
+
#
|
|
73
|
+
# However: we also want them to be able to create transactions, etc
|
|
74
|
+
# so we also make the session-factory available.
|
|
75
|
+
self.session = get_session_factory(self.engine)()
|
|
76
|
+
|
|
77
|
+
def commit(self):
|
|
78
|
+
""" Commit the current transaction state. """
|
|
79
|
+
return self.session.commit()
|
|
80
|
+
|
|
81
|
+
def lookup_station(self, name: str) -> orm.Station | None:
|
|
82
|
+
""" Use the database to lookup a station, which accepts a name that
|
|
83
|
+
is either a unique station name (or partial of one), or in the
|
|
84
|
+
'system name/station name' component. If the station does not
|
|
85
|
+
match a unique station, raises an AmbiguityError
|
|
86
|
+
"""
|
|
87
|
+
if "%" in name:
|
|
88
|
+
raise TradeException("wildcards ('%') are not supported in station names")
|
|
89
|
+
if "/" not in name:
|
|
90
|
+
if (station := self._station_lookup(name, exact=True, partial=False)):
|
|
91
|
+
return station
|
|
92
|
+
if self._system_lookup(name, exact=True, partial=False):
|
|
93
|
+
raise SystemNotStationError(f'"{name}" is a system name, use "/{name}" if you meant it as a station')
|
|
94
|
+
name = "/" + name
|
|
95
|
+
station: orm.Station | None = self.lookup_place(name)
|
|
96
|
+
return station
|
|
97
|
+
|
|
98
|
+
def lookup_system(self, name: str) -> orm.System | None:
|
|
99
|
+
""" Use the database to lookup a system, which accepts a name that
|
|
100
|
+
is either a unique system name (or partial of one), or in the
|
|
101
|
+
'system name/station name' component. If the system does not
|
|
102
|
+
match a unique system, raises an AmbiguityError
|
|
103
|
+
"""
|
|
104
|
+
if "%" in name:
|
|
105
|
+
raise TradeException("wildcards ('%') are not supported in system names")
|
|
106
|
+
system_name, _, _ = name.partition("/")
|
|
107
|
+
if not system_name:
|
|
108
|
+
raise TradeException(f"system name required for system lookup, got {name}")
|
|
109
|
+
result: orm.Station | orm.System | None = self.lookup_place(system_name)
|
|
110
|
+
if isinstance(result, orm.Station):
|
|
111
|
+
return result.system
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def lookup_place(self, name: str) -> orm.Station | orm.System | None:
|
|
115
|
+
""" Using a "[<system>]/[<station>]" style name, look up either a Station or a System."""
|
|
116
|
+
if "%" in name:
|
|
117
|
+
raise TradeException("wildcards ('%') are not supported in names")
|
|
118
|
+
sys_name, slashed, stn_name = name.partition("/")
|
|
119
|
+
if not slashed:
|
|
120
|
+
if stn_name:
|
|
121
|
+
station: orm.Station | None = self._station_lookup(stn_name, exact=True, partial=False)
|
|
122
|
+
if station:
|
|
123
|
+
return station
|
|
124
|
+
if sys_name:
|
|
125
|
+
system: orm.System | None = self._system_lookup(sys_name, exact=True, partial=False)
|
|
126
|
+
if system:
|
|
127
|
+
return system
|
|
128
|
+
|
|
129
|
+
if sys_name:
|
|
130
|
+
system = self._system_lookup(sys_name)
|
|
131
|
+
if not system:
|
|
132
|
+
raise TradeException(f"unknown system: {sys_name}")
|
|
133
|
+
if not stn_name:
|
|
134
|
+
return system
|
|
135
|
+
|
|
136
|
+
# Now we match the list of station names for this system.
|
|
137
|
+
stmt = self.session.query(orm.Station).filter(orm.Station.system_id == system.system_id).filter(orm.Station.name == stn_name)
|
|
138
|
+
results = stmt.all()
|
|
139
|
+
if len(results) == 1:
|
|
140
|
+
return results[0]
|
|
141
|
+
stmt = self.session.query(orm.Station).filter(orm.Station.system_id == system.system_id).filter(orm.Station.name.like(f"%{stn_name}%"))
|
|
142
|
+
results = stmt.all()
|
|
143
|
+
if len(results) > 1:
|
|
144
|
+
raise AmbiguityError("Station", stn_name, [s.name for s in results])
|
|
145
|
+
return results[0]
|
|
146
|
+
|
|
147
|
+
station = self._station_lookup(stn_name, exact=False)
|
|
148
|
+
return station
|
|
149
|
+
|
|
150
|
+
def _system_lookup(self, name: str, *, exact: bool = True, partial: bool = True) -> orm.System | None:
|
|
151
|
+
""" Look up a model by exact name match. """
|
|
152
|
+
assert exact or partial, "at least one of exact or partial must be True"
|
|
153
|
+
results: list[orm.System] | None = None
|
|
154
|
+
if exact:
|
|
155
|
+
results = self.session.query(orm.System).filter(orm.System.name == name).all()
|
|
156
|
+
if len(results) == 1:
|
|
157
|
+
partial = False
|
|
158
|
+
if partial:
|
|
159
|
+
like_pattern = f"%{name}%"
|
|
160
|
+
results = self.session.query(orm.System).filter(orm.System.name.like(like_pattern)).all()
|
|
161
|
+
|
|
162
|
+
if not results:
|
|
163
|
+
return None
|
|
164
|
+
if len(results) > 1:
|
|
165
|
+
raise AmbiguityError("System", name, results, key=lambda s: s.dbname())
|
|
166
|
+
return results[0]
|
|
167
|
+
|
|
168
|
+
def _station_lookup(self, name: str, *, exact: bool = True, partial: bool = True) -> orm.Station | None:
|
|
169
|
+
""" Look up a model by exact name match. """
|
|
170
|
+
assert exact or partial, "at least one of exact or partial must be True"
|
|
171
|
+
results: list[orm.Station] | None = None
|
|
172
|
+
if exact:
|
|
173
|
+
results = self.session.query(orm.Station).filter(orm.Station.name == name).all()
|
|
174
|
+
if len(results) == 1:
|
|
175
|
+
partial = False
|
|
176
|
+
if partial:
|
|
177
|
+
like_pattern = f"%{name}%"
|
|
178
|
+
results = self.session.query(orm.Station).filter(orm.Station.name.like(like_pattern)).all()
|
|
179
|
+
if not results:
|
|
180
|
+
return None
|
|
181
|
+
if len(results) > 1:
|
|
182
|
+
raise AmbiguityError("Station", name, results, key=lambda s: s.dbname())
|
|
183
|
+
return results[0]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from urllib.parse import urlparse, unquote
|
|
5
|
+
|
|
6
|
+
from .tradeexcept import TradeException
|
|
7
|
+
from .misc import progress as pbar
|
|
8
|
+
from . import fs
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
import typing
|
|
13
|
+
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if typing.TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
import os # for PathLike
|
|
20
|
+
from .tradeenv import TradeEnv
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
######################################################################
|
|
24
|
+
# Helpers
|
|
25
|
+
|
|
26
|
+
class HTTP404(TradeException):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def makeUnit(value: float) -> str:
|
|
31
|
+
num, unit = split_unit(value)
|
|
32
|
+
return f"{num}{unit}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def split_unit(value: float) -> tuple[str, str]:
|
|
36
|
+
"""
|
|
37
|
+
Split a byte size into a (number_str, unit_str) tuple.
|
|
38
|
+
Used when you need to colour or format the numeric part separately.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
>>> split_unit(30200000)
|
|
42
|
+
('28.8', 'MB')
|
|
43
|
+
"""
|
|
44
|
+
units = ["B", "KB", "MB", "GB", "TB"]
|
|
45
|
+
n = float(value)
|
|
46
|
+
i = 0
|
|
47
|
+
while n >= 1024.0 and i < len(units) - 1:
|
|
48
|
+
n /= 1024.0
|
|
49
|
+
i += 1
|
|
50
|
+
num_str = f"{n:.1f}" if i > 0 else f"{int(n)}"
|
|
51
|
+
return num_str, units[i]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_filename_from_url(url: str) -> str:
|
|
55
|
+
""" extracts just the filename from a url. """
|
|
56
|
+
return Path(unquote(urlparse(url).path)).name
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def download(
|
|
60
|
+
tdenv: TradeEnv,
|
|
61
|
+
url: str,
|
|
62
|
+
localFile: os.PathLike,
|
|
63
|
+
headers: dict | None = None,
|
|
64
|
+
backup: bool = False,
|
|
65
|
+
shebang: Callable | None = None,
|
|
66
|
+
chunkSize: int = 4096,
|
|
67
|
+
timeout: int = 30,
|
|
68
|
+
*,
|
|
69
|
+
length: int | str | None = None,
|
|
70
|
+
session: requests.Session | None = None,
|
|
71
|
+
):
|
|
72
|
+
"""
|
|
73
|
+
Fetch data from a URL and save the output
|
|
74
|
+
to a local file. Returns the response headers.
|
|
75
|
+
|
|
76
|
+
:param tdenv: TradeEnv we're working under
|
|
77
|
+
:param url: URL we're fetching (http, https or ftp)
|
|
78
|
+
:param localFile: Name of the local file to open.
|
|
79
|
+
:param headers: dict() of additional HTTP headers to send
|
|
80
|
+
:param shebang: function to call on the first line
|
|
81
|
+
"""
|
|
82
|
+
tdenv.NOTE("Requesting {}", url)
|
|
83
|
+
|
|
84
|
+
if isinstance(length, str):
|
|
85
|
+
length = int(length)
|
|
86
|
+
|
|
87
|
+
# If the caller provided an existing session stream, use that the fetch the request.
|
|
88
|
+
req = (session or requests).get(url, headers=headers or None, stream=True, timeout=timeout)
|
|
89
|
+
req.raise_for_status()
|
|
90
|
+
|
|
91
|
+
encoding = req.headers.get('content-encoding', 'uncompress')
|
|
92
|
+
content_length = req.headers.get('content-length', length)
|
|
93
|
+
transfer = req.headers.get('transfer-encoding', None)
|
|
94
|
+
if not length and transfer != 'chunked':
|
|
95
|
+
# chunked transfer-encoding doesn't need a content-length
|
|
96
|
+
if content_length is None:
|
|
97
|
+
print(req.headers)
|
|
98
|
+
raise TradeException("Remote server replied with invalid content-length.")
|
|
99
|
+
content_length = int(content_length)
|
|
100
|
+
if content_length <= 0:
|
|
101
|
+
raise TradeException(
|
|
102
|
+
"Remote server gave an empty response. Please try again later."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# if the file is being compressed by the server, the headers tell us the
|
|
106
|
+
# length of the compressed data, but in our loop below we will be receiving
|
|
107
|
+
# the uncompressed data, which should be larger, which will cause our
|
|
108
|
+
# download indicators to sit at 100% for a really long time if the file is
|
|
109
|
+
# heavily compressed and large (e.g spansh 1.5gb compressed vs 9GB uncompressed)
|
|
110
|
+
if length is None and encoding == "gzip" and content_length:
|
|
111
|
+
length = content_length * 3
|
|
112
|
+
|
|
113
|
+
if tdenv.detail > 1:
|
|
114
|
+
if length:
|
|
115
|
+
tdenv.NOTE("Downloading {} {}ed data", makeUnit(length), encoding)
|
|
116
|
+
else:
|
|
117
|
+
tdenv.NOTE("Downloading {} {}ed data", transfer, encoding)
|
|
118
|
+
tdenv.DEBUG0(str(req.headers).replace("{", "{{").replace("}", "}}"))
|
|
119
|
+
|
|
120
|
+
actPath = Path(localFile)
|
|
121
|
+
fs.ensurefolder(tdenv.tmpDir)
|
|
122
|
+
tmpPath = Path(tdenv.tmpDir, f"{actPath.name}.dl")
|
|
123
|
+
|
|
124
|
+
fetched = 0
|
|
125
|
+
started = time.time()
|
|
126
|
+
filename = get_filename_from_url(url)
|
|
127
|
+
with pbar.Progress(max_value=length, width=25, prefix=filename, style=pbar.CountingBar, show=not tdenv.quiet) as prog, tmpPath.open("wb") as fh:
|
|
128
|
+
for data in req.iter_content(chunk_size=chunkSize):
|
|
129
|
+
fh.write(data)
|
|
130
|
+
fetched += len(data)
|
|
131
|
+
if shebang:
|
|
132
|
+
bangLine = data.decode().partition("\n")[0]
|
|
133
|
+
tdenv.DEBUG0("Checking shebang of {}", bangLine)
|
|
134
|
+
shebang(bangLine)
|
|
135
|
+
shebang = None
|
|
136
|
+
if prog:
|
|
137
|
+
prog.increment(len(data))
|
|
138
|
+
tdenv.DEBUG0("End of data")
|
|
139
|
+
|
|
140
|
+
if not tdenv.quiet:
|
|
141
|
+
elapsed = (time.time() - started) or 1
|
|
142
|
+
num1, unit1 = split_unit(fetched)
|
|
143
|
+
num2, unit2 = split_unit(fetched / elapsed)
|
|
144
|
+
tdenv.NOTE(
|
|
145
|
+
f"Downloaded [cyan]{num1}[/]{unit1} of {encoding}ed data "
|
|
146
|
+
f"[cyan]{num2}[/]{unit2}/s"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
fs.ensurefolder(actPath.parent)
|
|
151
|
+
|
|
152
|
+
# Swap the file into place
|
|
153
|
+
if backup:
|
|
154
|
+
bakPath = Path(localFile + ".bak")
|
|
155
|
+
if bakPath.exists():
|
|
156
|
+
bakPath.unlink()
|
|
157
|
+
if actPath.exists():
|
|
158
|
+
actPath.rename(localFile + ".bak")
|
|
159
|
+
if actPath.exists():
|
|
160
|
+
actPath.unlink()
|
|
161
|
+
tmpPath.rename(actPath)
|
|
162
|
+
|
|
163
|
+
req.close()
|
|
164
|
+
return req.headers
|
|
165
|
+
|
|
166
|
+
def get_json_data(url, *, timeout: int = 90):
|
|
167
|
+
"""
|
|
168
|
+
Fetch JSON data from a URL and return the resulting dictionary.
|
|
169
|
+
|
|
170
|
+
Displays a progress bar as it downloads.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
req = requests.get(url, stream=True, timeout=timeout)
|
|
174
|
+
|
|
175
|
+
totalLength = req.headers.get('content-length')
|
|
176
|
+
if totalLength is None:
|
|
177
|
+
compression = req.headers.get('content-encoding')
|
|
178
|
+
compression = (compression + "'ed") if compression else "uncompressed"
|
|
179
|
+
print("Downloading {}: {}...".format(compression, url))
|
|
180
|
+
jsData = req.content
|
|
181
|
+
else:
|
|
182
|
+
totalLength = int(totalLength)
|
|
183
|
+
filename = get_filename_from_url(url)
|
|
184
|
+
progBar = pbar.Progress(totalLength, 25, prefix=filename)
|
|
185
|
+
|
|
186
|
+
jsData = bytes()
|
|
187
|
+
for data in req.iter_content():
|
|
188
|
+
jsData += data
|
|
189
|
+
progBar.increment(len(data))
|
|
190
|
+
progBar.clear()
|
|
191
|
+
|
|
192
|
+
return json.loads(jsData.decode())
|
tradedangerous/utils.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# --------------------------------------------------------------------
|
|
2
|
+
# Copyright (C) Oliver 'kfsone' Smith 2014 <oliver@kfs.org>:
|
|
3
|
+
# Copyright (C) Bernd 'Gazelle' Gollesch 2016, 2017
|
|
4
|
+
# Copyright (C) Jonathan 'eyeonus' Jones 2018, 2019
|
|
5
|
+
#
|
|
6
|
+
# You are free to use, redistribute, or even print and eat a copy of
|
|
7
|
+
# this software so long as you include this copyright notice.
|
|
8
|
+
# I guarantee there is at least one bug neither of us knew about.
|
|
9
|
+
# --------------------------------------------------------------------
|
|
10
|
+
# TradeDangerous :: Modules :: Utils
|
|
11
|
+
#
|
|
12
|
+
"""This modules contains various utils to be used internally.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
|
|
17
|
+
__all__ = ['titleFixup', 'checkForOcrDerp']
|
|
18
|
+
|
|
19
|
+
ocrDerp = re.compile(r"""(
|
|
20
|
+
^.$ |
|
|
21
|
+
LAN[O0]ING |
|
|
22
|
+
[O0][O0]CK |
|
|
23
|
+
[O0]INEILL |
|
|
24
|
+
AQUIRE[O0] |
|
|
25
|
+
FNT[EF]RPRIS[EF] |
|
|
26
|
+
[EF]NTFRPRIS[EF] |
|
|
27
|
+
[EF]NT[EF]RPRISF |
|
|
28
|
+
[O0](UTT|ALT)[O0]N |
|
|
29
|
+
8RA[DO0]LEY |
|
|
30
|
+
BRA[O0]LEY |
|
|
31
|
+
LLOY[O0] |
|
|
32
|
+
[O0]RBDAL |
|
|
33
|
+
DRB[O0]DAL |
|
|
34
|
+
[D0]RBITAL |
|
|
35
|
+
REE[O0] |
|
|
36
|
+
\BDOCK$ |
|
|
37
|
+
\BTERMINAL\b |
|
|
38
|
+
\bKID?[O0] |
|
|
39
|
+
\b[O3]E\b |
|
|
40
|
+
\bANDRA[O3]E\b |
|
|
41
|
+
\bAN[O3]RADE\b |
|
|
42
|
+
\bAN[O3]RA[O3]E\b |
|
|
43
|
+
VVELL\b |
|
|
44
|
+
[O0]IRAC\b |
|
|
45
|
+
\bVV |
|
|
46
|
+
\b[O0]ER?\b |
|
|
47
|
+
\b[O0]RAKE |
|
|
48
|
+
HAR[O0](T\b|W[I1L]CK) |
|
|
49
|
+
ACQU[I1L]R[E3][O0] |
|
|
50
|
+
\b[O0]ARK |
|
|
51
|
+
\b[O0]DAM |
|
|
52
|
+
[O0]EPOT |
|
|
53
|
+
\bMERE[O0] |
|
|
54
|
+
\b[O0]ENN?IS |
|
|
55
|
+
\bBRAN[o0] |
|
|
56
|
+
W[O0]{3} |
|
|
57
|
+
GO(D[O0]|[O0]D|[O0][O])ARD |
|
|
58
|
+
GO[DO0]{2}AR[O0] |
|
|
59
|
+
ORB[RH]AL\b |
|
|
60
|
+
\bJOR[O0]A |
|
|
61
|
+
\bST[O0]ART |
|
|
62
|
+
\bQUIMPY |
|
|
63
|
+
\bVAR[O0]E |
|
|
64
|
+
EN[^T]?ERPRISE |
|
|
65
|
+
EN..ERPRISE |
|
|
66
|
+
[E38]NT[E38]F[I1']?PR[I1'][S5][E38] |
|
|
67
|
+
\bMUR[O0]O |
|
|
68
|
+
\bBAR[O0]E |
|
|
69
|
+
\bBALLAR[O0] |
|
|
70
|
+
\b[O0]REYER\b |
|
|
71
|
+
\bEDWAR[O0] |
|
|
72
|
+
\bE[O0]WAR[DO0] |
|
|
73
|
+
III |
|
|
74
|
+
STARON\b |
|
|
75
|
+
\bST.ION\b |
|
|
76
|
+
\bSTION\b |
|
|
77
|
+
\BHANG[EA]R$ |
|
|
78
|
+
^\S+HUB$ |
|
|
79
|
+
\bLEBEOEV |
|
|
80
|
+
\B(
|
|
81
|
+
BASE |
|
|
82
|
+
ENTE[RP]P[RP]ISE |
|
|
83
|
+
TERMINA(L|II) |
|
|
84
|
+
P(L|II)ANT |
|
|
85
|
+
RELAY |
|
|
86
|
+
ORBITAL |
|
|
87
|
+
PLATFORM |
|
|
88
|
+
COLONY |
|
|
89
|
+
VISION |
|
|
90
|
+
REFINERY
|
|
91
|
+
)$ |
|
|
92
|
+
[O0]RB[I1]DAL |
|
|
93
|
+
[O0]R[DL][I1]TAL |
|
|
94
|
+
\bBRIOGER |
|
|
95
|
+
\bJUOSON |
|
|
96
|
+
LANOER |
|
|
97
|
+
G[O0][O0]([RW]|VV)[O0I]N |
|
|
98
|
+
\bSPE([O0][DO0]|[DO0][O0])ING\b |
|
|
99
|
+
\bARCHIMEOES\b |
|
|
100
|
+
\bH[O0D]L[O0]ING |
|
|
101
|
+
\bM[O0D]HMAN[O0] |
|
|
102
|
+
\b[O0]ANA\b |
|
|
103
|
+
\bALEKSAN[O0]R[O0D]V\b |
|
|
104
|
+
\bCH[0D]MSKY\b |
|
|
105
|
+
\b[O0]IESEL\b |
|
|
106
|
+
[O0]{3} |
|
|
107
|
+
SCHMI[O0]T |
|
|
108
|
+
\bSAUN[O0]ER |
|
|
109
|
+
[O0]IV[E3] |
|
|
110
|
+
VIRIDN$ |
|
|
111
|
+
\bHORI\.ONS |
|
|
112
|
+
C[O0D]+LNY$ |
|
|
113
|
+
\bR[O0]ZH[O0]E[S5]TVENSKY |
|
|
114
|
+
\bRDZH[DO0]ESTVENSKY |
|
|
115
|
+
'' |
|
|
116
|
+
^[^A-Z0-9] |
|
|
117
|
+
\s{2,} |
|
|
118
|
+
^OEN |
|
|
119
|
+
^MCK(EF|FE)\b |
|
|
120
|
+
\bCHAN\s+DLER |
|
|
121
|
+
\b[O0]UMONT |
|
|
122
|
+
\bUN[0O]ER |
|
|
123
|
+
\bSDMM |
|
|
124
|
+
\bREA([O0]D|D[O0]) |
|
|
125
|
+
\bRD[DO0][DO0]EN |
|
|
126
|
+
\bR[O0]([O0]D|D[O0])EN |
|
|
127
|
+
(?<!BR)[O0]ECK$ |
|
|
128
|
+
SE?TTL(FMENT|EMFNT|FMFNT) |
|
|
129
|
+
SE?TTLMENT |
|
|
130
|
+
STTL[E38]?M[E38]?NT |
|
|
131
|
+
S\s?[E38]\s?T\s?T\s?L\s?[E38](\sM|M\s)[E38]\s?N\s?T$ |
|
|
132
|
+
S[E38]TT[38]?L[E38]MNT |
|
|
133
|
+
MARKFT |
|
|
134
|
+
HANGFR |
|
|
135
|
+
CL(EVF|FVE|FVF) |
|
|
136
|
+
\bCRY$ |
|
|
137
|
+
G[E3][D0]RG[E3]LUCA[S5] |
|
|
138
|
+
PLAT[E3]F[D0]RM |
|
|
139
|
+
OCONNOR |
|
|
140
|
+
` |
|
|
141
|
+
-- |
|
|
142
|
+
\bREILLI\b |
|
|
143
|
+
\bRIN[FC]\b |
|
|
144
|
+
\bOL[E3]ARY |
|
|
145
|
+
\bSATION\b |
|
|
146
|
+
,\w |
|
|
147
|
+
\bI?NGLY\b |
|
|
148
|
+
\bAU\sL[DO0]\b |
|
|
149
|
+
(^|\s)['.] |
|
|
150
|
+
^- | -$ |
|
|
151
|
+
\bDREBBFL\b
|
|
152
|
+
\bLEVIE |
|
|
153
|
+
\bRN\b |
|
|
154
|
+
\bH\sUNZIKER |
|
|
155
|
+
\bL[O0D]FTH\sUS |
|
|
156
|
+
\bHORNUCH\b |
|
|
157
|
+
\bKLU\sDZE |
|
|
158
|
+
^[DR]HN\b |
|
|
159
|
+
SU\sI?RVEY$ |
|
|
160
|
+
H[DO0]L[O0]ING |
|
|
161
|
+
H[D0]LDING |
|
|
162
|
+
M[DO0]HMAN[O0] |
|
|
163
|
+
\bABL\b |
|
|
164
|
+
\bBENNET\b |
|
|
165
|
+
\bHU8\b |
|
|
166
|
+
\sCITV$ |
|
|
167
|
+
\sPIT[VY]$ |
|
|
168
|
+
\bTFR |
|
|
169
|
+
IVII |
|
|
170
|
+
\BINAI$ |
|
|
171
|
+
SET[IT]''LEMEN |
|
|
172
|
+
I'L | R'I | (^|\s)'L | [^Ss]'(?=\s|$) |
|
|
173
|
+
^I \s (?! [Ss][Oo][Ll][Aa]) |
|
|
174
|
+
\bA7\S |
|
|
175
|
+
\sH\sI?UB$ |
|
|
176
|
+
\bALEDNDRIA\b |
|
|
177
|
+
\sH\sU\sB$ |
|
|
178
|
+
\bC[O0]LCNY\b |
|
|
179
|
+
\bOOCTE\b |
|
|
180
|
+
\bBULGAFIIN\b |
|
|
181
|
+
\bWH\sIEEL |
|
|
182
|
+
\bBR8NNAN\b |
|
|
183
|
+
\b(ID)?ING$ |
|
|
184
|
+
GATEVVAY$ |
|
|
185
|
+
[HI\s]U\sI?B$ |
|
|
186
|
+
CLAI\sI?M$ |
|
|
187
|
+
\bUITY$ |
|
|
188
|
+
\bDING$ |
|
|
189
|
+
BTOP$ |
|
|
190
|
+
B'I'OP$ |
|
|
191
|
+
TRANQUNUTY$ |
|
|
192
|
+
C[O0]LUNY$ |
|
|
193
|
+
\bMAGN\sI?US\b |
|
|
194
|
+
\s-[A-Z] |
|
|
195
|
+
^NAKAM(\sIU|U\sIR) |
|
|
196
|
+
^THOM\sI?PSON |
|
|
197
|
+
^STEPH\sI?ENSON |
|
|
198
|
+
\bCQRK\b |
|
|
199
|
+
^AN\sI?DREW |
|
|
200
|
+
^WATSO\sIN |
|
|
201
|
+
^QSSW |
|
|
202
|
+
^RTZEN |
|
|
203
|
+
\bI?NAL$ |
|
|
204
|
+
\b''I\b
|
|
205
|
+
)""", flags=re.X)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def titleFixup(text):
|
|
209
|
+
"""
|
|
210
|
+
Correct case in a word assuming the presence of titles/surnames,
|
|
211
|
+
including 'McDonald', 'MacNair', 'McKilroy', and cases that
|
|
212
|
+
python's title screws up such as "Smith's".
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
text = text.title()
|
|
216
|
+
text = re.sub(
|
|
217
|
+
r"\b(Mc)([a-z])",
|
|
218
|
+
lambda match: match.group(1) + match.group(2).upper(),
|
|
219
|
+
text
|
|
220
|
+
)
|
|
221
|
+
text = re.sub(
|
|
222
|
+
r"\b(Mac)([bcdfgjklmnpqrstvwxyz])([a-z]{3,})",
|
|
223
|
+
lambda m: m.group(1) + m.group(2).upper() + m.group(3),
|
|
224
|
+
text
|
|
225
|
+
)
|
|
226
|
+
text = re.sub("\b(von|van|de|du|of)\b",
|
|
227
|
+
lambda m: m.group(1).lower,
|
|
228
|
+
text
|
|
229
|
+
)
|
|
230
|
+
text = re.sub(r"'S\b", "'s", text)
|
|
231
|
+
text = ''.join((text[0].upper(), text[1:]))
|
|
232
|
+
|
|
233
|
+
return text
|
|
234
|
+
|
|
235
|
+
def checkForOcrDerp(tdenv, systemName, stationName):
|
|
236
|
+
match = ocrDerp.search(stationName.upper())
|
|
237
|
+
if match:
|
|
238
|
+
tdenv.NOTE(
|
|
239
|
+
"Ignoring '{}/{}' because it looks like OCR derp."
|
|
240
|
+
.format(systemName, stationName)
|
|
241
|
+
)
|
|
242
|
+
return match
|
|
243
|
+
return None
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# --------------------------------------------------------------------
|
|
3
|
+
# Copyright (C) Oliver 'kfsone' Smith 2014 <oliver@kfs.org>:
|
|
4
|
+
# Copyright (C) Bernd 'Gazelle' Gollesch 2016, 2017
|
|
5
|
+
# Copyright (C) Jonathan 'eyeonus' Jones 2018, 2019
|
|
6
|
+
#
|
|
7
|
+
# You are free to use, redistribute, or even print and eat a copy of
|
|
8
|
+
# this software so long as you include this copyright notice.
|
|
9
|
+
# I guarantee there is at least one bug neither of us knew about.
|
|
10
|
+
# --------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
"""just keeper of current version"""
|
|
13
|
+
|
|
14
|
+
# TODO: remember to update tests when version changes
|
|
15
|
+
__version__ = '12.7.6'
|
|
16
|
+
|