tradedangerous 12.0.0__py3-none-any.whl → 12.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/db/__init__.py +27 -0
- tradedangerous/db/adapter.py +191 -0
- tradedangerous/db/config.py +95 -0
- tradedangerous/db/engine.py +246 -0
- tradedangerous/db/lifecycle.py +332 -0
- tradedangerous/db/locks.py +208 -0
- tradedangerous/db/orm_models.py +455 -0
- tradedangerous/db/paths.py +112 -0
- tradedangerous/db/utils.py +661 -0
- tradedangerous/version.py +1 -1
- {tradedangerous-12.0.0.dist-info → tradedangerous-12.0.1.dist-info}/METADATA +6 -4
- {tradedangerous-12.0.0.dist-info → tradedangerous-12.0.1.dist-info}/RECORD +16 -7
- {tradedangerous-12.0.0.dist-info → tradedangerous-12.0.1.dist-info}/WHEEL +0 -0
- {tradedangerous-12.0.0.dist-info → tradedangerous-12.0.1.dist-info}/entry_points.txt +0 -0
- {tradedangerous-12.0.0.dist-info → tradedangerous-12.0.1.dist-info}/licenses/LICENSE +0 -0
- {tradedangerous-12.0.0.dist-info → tradedangerous-12.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# tradedangerous/db/orm_models.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from sqlalchemy import (
|
|
7
|
+
MetaData,
|
|
8
|
+
ForeignKey,
|
|
9
|
+
Integer,
|
|
10
|
+
BigInteger,
|
|
11
|
+
String,
|
|
12
|
+
CHAR,
|
|
13
|
+
Enum,
|
|
14
|
+
Index,
|
|
15
|
+
UniqueConstraint,
|
|
16
|
+
CheckConstraint,
|
|
17
|
+
text,
|
|
18
|
+
Column,
|
|
19
|
+
DateTime,
|
|
20
|
+
)
|
|
21
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, object_session
|
|
22
|
+
from sqlalchemy.sql import expression
|
|
23
|
+
from sqlalchemy.ext.compiler import compiles
|
|
24
|
+
from sqlalchemy.types import TypeDecorator
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---- Dialect-aware time utilities (moved before model usage) ----
|
|
28
|
+
class now6(expression.FunctionElement):
|
|
29
|
+
"""CURRENT_TIMESTAMP with microseconds on MySQL/MariaDB; plain CURRENT_TIMESTAMP elsewhere."""
|
|
30
|
+
type = DateTime()
|
|
31
|
+
inherit_cache = True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@compiles(now6, "mysql")
|
|
35
|
+
@compiles(now6, "mariadb")
|
|
36
|
+
def _mysql_now6(element, compiler, **kw):
|
|
37
|
+
return "CURRENT_TIMESTAMP(6)"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@compiles(now6)
|
|
41
|
+
def _default_now(element, compiler, **kw):
|
|
42
|
+
return "CURRENT_TIMESTAMP"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DateTime6(TypeDecorator):
|
|
46
|
+
"""DATETIME that is DATETIME(6) on MySQL/MariaDB, generic DateTime elsewhere."""
|
|
47
|
+
impl = DateTime
|
|
48
|
+
cache_ok = True
|
|
49
|
+
|
|
50
|
+
def load_dialect_impl(self, dialect):
|
|
51
|
+
if dialect.name in ("mysql", "mariadb"):
|
|
52
|
+
from sqlalchemy.dialects.mysql import DATETIME as _MYSQL_DATETIME
|
|
53
|
+
return dialect.type_descriptor(_MYSQL_DATETIME(fsp=6))
|
|
54
|
+
return dialect.type_descriptor(DateTime())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ---------- Dialect Helpers --------
|
|
58
|
+
class CIString(TypeDecorator):
|
|
59
|
+
"""
|
|
60
|
+
Case-insensitive string type.
|
|
61
|
+
- SQLite → uses NOCASE collation
|
|
62
|
+
- MySQL/MariaDB → uses utf8mb4_unicode_ci
|
|
63
|
+
- Others → plain String
|
|
64
|
+
"""
|
|
65
|
+
impl = String
|
|
66
|
+
cache_ok = True
|
|
67
|
+
|
|
68
|
+
def __init__(self, length, **kwargs):
|
|
69
|
+
super().__init__(length=length, **kwargs)
|
|
70
|
+
|
|
71
|
+
def load_dialect_impl(self, dialect):
|
|
72
|
+
if dialect.name == "sqlite":
|
|
73
|
+
return dialect.type_descriptor(String(self.impl.length, collation="NOCASE"))
|
|
74
|
+
elif dialect.name in ("mysql", "mariadb"):
|
|
75
|
+
return dialect.type_descriptor(String(self.impl.length, collation="utf8mb4_unicode_ci"))
|
|
76
|
+
else:
|
|
77
|
+
return dialect.type_descriptor(String(self.impl.length))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------- Naming & Base ----------
|
|
81
|
+
naming_convention = {
|
|
82
|
+
"ix": "ix_%(table_name)s__%(column_0_N_name)s",
|
|
83
|
+
"uq": "uq_%(table_name)s__%(column_0_N_name)s",
|
|
84
|
+
"ck": "ck_%(table_name)s__%(column_0_name)s", # use column name, not constraint_name
|
|
85
|
+
"fk": "fk_%(table_name)s__%(column_0_N_name)s__%(referred_table_name)s",
|
|
86
|
+
"pk": "pk_%(table_name)s",
|
|
87
|
+
}
|
|
88
|
+
metadata = MetaData(naming_convention=naming_convention)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Base(DeclarativeBase):
|
|
92
|
+
metadata = metadata
|
|
93
|
+
|
|
94
|
+
# ---------- Enums ----------
|
|
95
|
+
TriState = Enum(
|
|
96
|
+
"Y",
|
|
97
|
+
"N",
|
|
98
|
+
"?",
|
|
99
|
+
native_enum=False,
|
|
100
|
+
create_constraint=True,
|
|
101
|
+
validate_strings=True,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
PadSize = Enum(
|
|
105
|
+
"S",
|
|
106
|
+
"M",
|
|
107
|
+
"L",
|
|
108
|
+
"?",
|
|
109
|
+
native_enum=False,
|
|
110
|
+
create_constraint=True,
|
|
111
|
+
validate_strings=True,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------- Core Domain ----------
|
|
116
|
+
class Added(Base):
|
|
117
|
+
__tablename__ = "Added"
|
|
118
|
+
|
|
119
|
+
added_id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
120
|
+
name: Mapped[str] = mapped_column(CIString(128), nullable=False, unique=True)
|
|
121
|
+
|
|
122
|
+
# Relationships
|
|
123
|
+
systems: Mapped[list["System"]] = relationship(back_populates="added")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class System(Base):
|
|
127
|
+
__tablename__ = "System"
|
|
128
|
+
|
|
129
|
+
system_id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
130
|
+
name: Mapped[str] = mapped_column(CIString(128), nullable=False)
|
|
131
|
+
pos_x: Mapped[float] = mapped_column(nullable=False)
|
|
132
|
+
pos_y: Mapped[float] = mapped_column(nullable=False)
|
|
133
|
+
pos_z: Mapped[float] = mapped_column(nullable=False)
|
|
134
|
+
added_id: Mapped[int | None] = mapped_column(
|
|
135
|
+
ForeignKey("Added.added_id", onupdate="CASCADE", ondelete="CASCADE")
|
|
136
|
+
)
|
|
137
|
+
modified: Mapped[str] = mapped_column(
|
|
138
|
+
DateTime6(),
|
|
139
|
+
server_default=now6(),
|
|
140
|
+
onupdate=now6(),
|
|
141
|
+
nullable=False,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Relationships
|
|
145
|
+
added: Mapped[Optional["Added"]] = relationship(back_populates="systems")
|
|
146
|
+
stations: Mapped[list["Station"]] = relationship(back_populates="system", cascade="all, delete-orphan")
|
|
147
|
+
|
|
148
|
+
__table_args__ = (
|
|
149
|
+
Index("idx_system_by_pos", "pos_x", "pos_y", "pos_z", "system_id"),
|
|
150
|
+
Index("idx_system_by_name", "name"),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class Station(Base):
|
|
157
|
+
__tablename__ = "Station"
|
|
158
|
+
|
|
159
|
+
station_id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
160
|
+
name: Mapped[str] = mapped_column(CIString(128), nullable=False)
|
|
161
|
+
|
|
162
|
+
# type widened; cascade semantics unchanged (DELETE only)
|
|
163
|
+
system_id: Mapped[int] = mapped_column(
|
|
164
|
+
BigInteger,
|
|
165
|
+
ForeignKey("System.system_id", ondelete="CASCADE"),
|
|
166
|
+
nullable=False,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
ls_from_star: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("0"))
|
|
170
|
+
|
|
171
|
+
blackmarket: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
172
|
+
max_pad_size: Mapped[str] = mapped_column(PadSize, nullable=False, server_default=text("'?'"))
|
|
173
|
+
market: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
174
|
+
shipyard: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
175
|
+
outfitting: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
176
|
+
rearm: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
177
|
+
refuel: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
178
|
+
repair: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
179
|
+
planetary: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
180
|
+
|
|
181
|
+
type_id: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("0"))
|
|
182
|
+
modified: Mapped[str] = mapped_column(DateTime6(), server_default=now6(), onupdate=now6(), nullable=False)
|
|
183
|
+
|
|
184
|
+
# Relationships
|
|
185
|
+
system: Mapped["System"] = relationship(back_populates="stations")
|
|
186
|
+
items: Mapped[list["StationItem"]] = relationship(back_populates="station", cascade="all, delete-orphan")
|
|
187
|
+
ship_vendors: Mapped[list["ShipVendor"]] = relationship(back_populates="station", cascade="all, delete-orphan")
|
|
188
|
+
upgrade_vendors: Mapped[list["UpgradeVendor"]] = relationship(back_populates="station", cascade="all, delete-orphan")
|
|
189
|
+
|
|
190
|
+
__table_args__ = (
|
|
191
|
+
Index("idx_station_by_system", "system_id"),
|
|
192
|
+
Index("idx_station_by_name", "name"),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class Category(Base):
|
|
199
|
+
__tablename__ = "Category"
|
|
200
|
+
|
|
201
|
+
category_id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
202
|
+
name: Mapped[str] = mapped_column(CIString(128), nullable=False)
|
|
203
|
+
|
|
204
|
+
# Relationships
|
|
205
|
+
items: Mapped[list["Item"]] = relationship(back_populates="category")
|
|
206
|
+
|
|
207
|
+
__table_args__ = (Index("idx_category_by_name", "name"),)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class Item(Base):
|
|
211
|
+
__tablename__ = "Item"
|
|
212
|
+
|
|
213
|
+
item_id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
214
|
+
name: Mapped[str] = mapped_column(CIString(128), nullable=False)
|
|
215
|
+
category_id: Mapped[int] = mapped_column(
|
|
216
|
+
ForeignKey("Category.category_id", onupdate="CASCADE", ondelete="CASCADE"),
|
|
217
|
+
nullable=False,
|
|
218
|
+
)
|
|
219
|
+
ui_order: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("0"))
|
|
220
|
+
avg_price: Mapped[int | None] = mapped_column(Integer)
|
|
221
|
+
fdev_id: Mapped[int | None] = mapped_column(Integer)
|
|
222
|
+
|
|
223
|
+
# Relationships
|
|
224
|
+
category: Mapped["Category"] = relationship(back_populates="items")
|
|
225
|
+
stations: Mapped[list["StationItem"]] = relationship(back_populates="item", cascade="all, delete-orphan")
|
|
226
|
+
|
|
227
|
+
__table_args__ = (
|
|
228
|
+
Index("idx_item_by_fdevid", "fdev_id"),
|
|
229
|
+
Index("idx_item_by_category", "category_id"),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class StationItem(Base):
|
|
235
|
+
__tablename__ = "StationItem"
|
|
236
|
+
|
|
237
|
+
station_id: Mapped[int] = mapped_column(
|
|
238
|
+
BigInteger,
|
|
239
|
+
ForeignKey("Station.station_id", ondelete="CASCADE", onupdate="CASCADE"),
|
|
240
|
+
primary_key=True,
|
|
241
|
+
)
|
|
242
|
+
item_id: Mapped[int] = mapped_column(
|
|
243
|
+
ForeignKey("Item.item_id", ondelete="CASCADE", onupdate="CASCADE"),
|
|
244
|
+
primary_key=True,
|
|
245
|
+
)
|
|
246
|
+
demand_price: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
247
|
+
demand_units: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
248
|
+
demand_level: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
249
|
+
supply_price: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
250
|
+
supply_units: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
251
|
+
supply_level: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
252
|
+
modified: Mapped[str] = mapped_column(DateTime6(), server_default=now6(), onupdate=now6(), nullable=False)
|
|
253
|
+
from_live: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("0"))
|
|
254
|
+
|
|
255
|
+
# Relationships
|
|
256
|
+
station: Mapped["Station"] = relationship(back_populates="items")
|
|
257
|
+
item: Mapped["Item"] = relationship(back_populates="stations")
|
|
258
|
+
|
|
259
|
+
__table_args__ = (
|
|
260
|
+
Index("si_mod_stn_itm", "modified", "station_id", "item_id"),
|
|
261
|
+
Index("si_itm_dmdpr", "item_id", "demand_price", sqlite_where=text("demand_price > 0")),
|
|
262
|
+
Index("si_itm_suppr", "item_id", "supply_price", sqlite_where=text("supply_price > 0")),
|
|
263
|
+
{"sqlite_with_rowid": False},
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class Ship(Base):
|
|
270
|
+
__tablename__ = "Ship"
|
|
271
|
+
|
|
272
|
+
ship_id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
273
|
+
name: Mapped[str] = mapped_column(CIString(128), nullable=False)
|
|
274
|
+
cost: Mapped[int | None] = mapped_column(Integer)
|
|
275
|
+
|
|
276
|
+
# Relationships
|
|
277
|
+
vendors: Mapped[list["ShipVendor"]] = relationship(back_populates="ship")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class ShipVendor(Base):
|
|
281
|
+
__tablename__ = "ShipVendor"
|
|
282
|
+
|
|
283
|
+
ship_id: Mapped[int] = mapped_column(
|
|
284
|
+
ForeignKey("Ship.ship_id", ondelete="CASCADE", onupdate="CASCADE"),
|
|
285
|
+
primary_key=True,
|
|
286
|
+
)
|
|
287
|
+
station_id: Mapped[int] = mapped_column(
|
|
288
|
+
BigInteger,
|
|
289
|
+
ForeignKey("Station.station_id", ondelete="CASCADE", onupdate="CASCADE"),
|
|
290
|
+
primary_key=True,
|
|
291
|
+
)
|
|
292
|
+
modified: Mapped[str] = mapped_column(DateTime6(), server_default=now6(), onupdate=now6(), nullable=False)
|
|
293
|
+
|
|
294
|
+
# Relationships
|
|
295
|
+
ship: Mapped["Ship"] = relationship(back_populates="vendors")
|
|
296
|
+
station: Mapped["Station"] = relationship(back_populates="ship_vendors")
|
|
297
|
+
|
|
298
|
+
__table_args__ = (Index("idx_shipvendor_by_station", "station_id"),)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class Upgrade(Base):
|
|
302
|
+
__tablename__ = "Upgrade"
|
|
303
|
+
|
|
304
|
+
upgrade_id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
305
|
+
name: Mapped[str] = mapped_column(CIString(128), nullable=False)
|
|
306
|
+
class_: Mapped[int] = mapped_column("class", Integer, nullable=False)
|
|
307
|
+
rating: Mapped[str] = mapped_column(CHAR(1), nullable=False)
|
|
308
|
+
ship: Mapped[str | None] = mapped_column(CIString(128))
|
|
309
|
+
|
|
310
|
+
# Relationships
|
|
311
|
+
vendors: Mapped[list["UpgradeVendor"]] = relationship(back_populates="upgrade")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class UpgradeVendor(Base):
|
|
315
|
+
__tablename__ = "UpgradeVendor"
|
|
316
|
+
|
|
317
|
+
upgrade_id: Mapped[int] = mapped_column(
|
|
318
|
+
ForeignKey("Upgrade.upgrade_id", ondelete="CASCADE", onupdate="CASCADE"),
|
|
319
|
+
primary_key=True,
|
|
320
|
+
)
|
|
321
|
+
station_id: Mapped[int] = mapped_column(
|
|
322
|
+
BigInteger,
|
|
323
|
+
ForeignKey("Station.station_id", ondelete="CASCADE", onupdate="CASCADE"),
|
|
324
|
+
primary_key=True,
|
|
325
|
+
)
|
|
326
|
+
modified: Mapped[str] = mapped_column(DateTime6(), nullable=False, server_default=now6(), onupdate=now6())
|
|
327
|
+
|
|
328
|
+
# Relationships
|
|
329
|
+
upgrade: Mapped["Upgrade"] = relationship(back_populates="vendors")
|
|
330
|
+
station: Mapped["Station"] = relationship(back_populates="upgrade_vendors")
|
|
331
|
+
|
|
332
|
+
__table_args__ = (Index("idx_vendor_by_station_id", "station_id"),)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class RareItem(Base):
|
|
336
|
+
__tablename__ = "RareItem"
|
|
337
|
+
|
|
338
|
+
rare_id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
339
|
+
station_id: Mapped[int] = mapped_column(
|
|
340
|
+
BigInteger,
|
|
341
|
+
ForeignKey("Station.station_id", ondelete="CASCADE", onupdate="CASCADE"),
|
|
342
|
+
nullable=False,
|
|
343
|
+
)
|
|
344
|
+
category_id: Mapped[int] = mapped_column(
|
|
345
|
+
ForeignKey("Category.category_id", onupdate="CASCADE", ondelete="CASCADE"),
|
|
346
|
+
nullable=False,
|
|
347
|
+
)
|
|
348
|
+
name: Mapped[str] = mapped_column(CIString(128), nullable=False)
|
|
349
|
+
cost: Mapped[int | None] = mapped_column(Integer)
|
|
350
|
+
max_allocation: Mapped[int | None] = mapped_column(Integer)
|
|
351
|
+
illegal: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
352
|
+
suppressed: Mapped[str] = mapped_column(TriState, nullable=False, server_default=text("'?'"))
|
|
353
|
+
|
|
354
|
+
__table_args__ = (UniqueConstraint("name", name="uq_rareitem_name"),)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class FDevShipyard(Base):
|
|
360
|
+
__tablename__ = "FDevShipyard"
|
|
361
|
+
|
|
362
|
+
id = Column(Integer, primary_key=True, unique=True, nullable=False)
|
|
363
|
+
symbol = Column(CIString(128))
|
|
364
|
+
name = Column(CIString(128))
|
|
365
|
+
entitlement = Column(String(50))
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class FDevOutfitting(Base):
|
|
369
|
+
__tablename__ = "FDevOutfitting"
|
|
370
|
+
|
|
371
|
+
id = Column(Integer, primary_key=True, unique=True, nullable=False)
|
|
372
|
+
symbol = Column(CIString(128))
|
|
373
|
+
category = Column(String(10))
|
|
374
|
+
name = Column(CIString(128))
|
|
375
|
+
mount = Column(String(20))
|
|
376
|
+
guidance = Column(String(20))
|
|
377
|
+
ship = Column(CIString(128))
|
|
378
|
+
class_ = Column("class", String(1), nullable=False)
|
|
379
|
+
rating = Column(String(1), nullable=False)
|
|
380
|
+
entitlement = Column(String(50))
|
|
381
|
+
|
|
382
|
+
__table_args__ = (
|
|
383
|
+
CheckConstraint(
|
|
384
|
+
"category IN ('hardpoint','internal','standard','utility')",
|
|
385
|
+
name="ck_fdo_category",
|
|
386
|
+
),
|
|
387
|
+
CheckConstraint(
|
|
388
|
+
"(mount IN ('Fixed','Gimballed','Turreted')) OR (mount IS NULL)",
|
|
389
|
+
name="ck_fdo_mount",
|
|
390
|
+
),
|
|
391
|
+
CheckConstraint(
|
|
392
|
+
"(guidance IN ('Dumbfire','Seeker','Swarm')) OR (guidance IS NULL)",
|
|
393
|
+
name="ck_fdo_guidance",
|
|
394
|
+
),
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# ---------- Control & Staging ----------
|
|
399
|
+
class ExportControl(Base):
|
|
400
|
+
"""
|
|
401
|
+
Singleton control row for hybrid export/watermarking.
|
|
402
|
+
- id: always 1
|
|
403
|
+
- last_full_dump_time: watermark
|
|
404
|
+
- last_reset_key: optional cursor for chunked from_live resets
|
|
405
|
+
"""
|
|
406
|
+
__tablename__ = "ExportControl"
|
|
407
|
+
|
|
408
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, server_default=text("1"))
|
|
409
|
+
last_full_dump_time: Mapped[str] = mapped_column(DateTime6(), nullable=False)
|
|
410
|
+
last_reset_key: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class StationItemStaging(Base):
|
|
414
|
+
"""
|
|
415
|
+
Staging table for bulk loads (no FKs). Same columns as StationItem.
|
|
416
|
+
"""
|
|
417
|
+
__tablename__ = "StationItem_staging"
|
|
418
|
+
|
|
419
|
+
station_id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
|
|
420
|
+
item_id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
421
|
+
demand_price: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
422
|
+
demand_units: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
423
|
+
demand_level: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
424
|
+
supply_price: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
425
|
+
supply_units: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
426
|
+
supply_level: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
427
|
+
modified: Mapped[str] = mapped_column(DateTime6(), server_default=now6(), onupdate=now6(), nullable=False)
|
|
428
|
+
from_live: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text("0"))
|
|
429
|
+
|
|
430
|
+
__table_args__ = (Index("idx_sistaging_stn_itm", "station_id", "item_id"),)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
__all__ = [
|
|
436
|
+
# Base
|
|
437
|
+
"Base",
|
|
438
|
+
# Core
|
|
439
|
+
"Added",
|
|
440
|
+
"System",
|
|
441
|
+
"Station",
|
|
442
|
+
"Category",
|
|
443
|
+
"Item",
|
|
444
|
+
"StationItem",
|
|
445
|
+
"Ship",
|
|
446
|
+
"ShipVendor",
|
|
447
|
+
"Upgrade",
|
|
448
|
+
"UpgradeVendor",
|
|
449
|
+
"RareItem",
|
|
450
|
+
"FDevShipyard",
|
|
451
|
+
"FDevOutfitting",
|
|
452
|
+
# Control & staging
|
|
453
|
+
"ExportControl",
|
|
454
|
+
"StationItemStaging",
|
|
455
|
+
]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# tradedangerous/db/paths.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import configparser
|
|
10
|
+
except Exception: # pragma: no cover
|
|
11
|
+
configparser = None # type: ignore
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ensure_dir",
|
|
15
|
+
"resolve_data_dir",
|
|
16
|
+
"resolve_tmp_dir",
|
|
17
|
+
"resolve_db_config_path",
|
|
18
|
+
"get_sqlite_db_path",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
# --------------------------
|
|
22
|
+
# Helpers that tolerate either ConfigParser or dict
|
|
23
|
+
# --------------------------
|
|
24
|
+
|
|
25
|
+
def _is_cfg(obj: Any) -> bool:
|
|
26
|
+
return hasattr(obj, "has_option") and hasattr(obj, "get")
|
|
27
|
+
|
|
28
|
+
def _get_opt(cfg: Any, section: str, key: str, default: str | None = None) -> str | None:
|
|
29
|
+
"""Return option from either a ConfigParser-like object or a nested dict.
|
|
30
|
+
Falls back to [database] overlay for convenience, matching legacy behaviour.
|
|
31
|
+
Safe even if cfg is None.
|
|
32
|
+
"""
|
|
33
|
+
if cfg is None:
|
|
34
|
+
return default
|
|
35
|
+
# ConfigParser branch
|
|
36
|
+
if _is_cfg(cfg):
|
|
37
|
+
try:
|
|
38
|
+
if cfg.has_option(section, key):
|
|
39
|
+
return cfg.get(section, key) # type: ignore[arg-type]
|
|
40
|
+
if cfg.has_option("database", key):
|
|
41
|
+
return cfg.get("database", key) # type: ignore[arg-type]
|
|
42
|
+
except Exception:
|
|
43
|
+
return default
|
|
44
|
+
return default
|
|
45
|
+
# Mapping/dict branch
|
|
46
|
+
try:
|
|
47
|
+
sec = (cfg or {}).get(section, {}) or {}
|
|
48
|
+
if key in sec and sec[key] not in (None, ""):
|
|
49
|
+
return sec[key]
|
|
50
|
+
db = (cfg or {}).get("database", {}) or {}
|
|
51
|
+
if key in db and db[key] not in (None, ""):
|
|
52
|
+
return db[key]
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
return default
|
|
56
|
+
|
|
57
|
+
def _resolve_dir(default_rel: str, env_key: str, cfg_value: str | None) -> Path:
|
|
58
|
+
cand = os.getenv(env_key) or (cfg_value or default_rel)
|
|
59
|
+
p = Path(cand).expanduser()
|
|
60
|
+
return p if p.is_absolute() else (Path.cwd() / p)
|
|
61
|
+
|
|
62
|
+
# --------------------------
|
|
63
|
+
# Public API
|
|
64
|
+
# --------------------------
|
|
65
|
+
|
|
66
|
+
def ensure_dir(pathlike: os.PathLike | str) -> Path:
|
|
67
|
+
"""Create directory if missing (idempotent) and return the Path."""
|
|
68
|
+
p = Path(pathlike)
|
|
69
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
return p
|
|
71
|
+
|
|
72
|
+
def resolve_data_dir(cfg: Any = None) -> Path:
|
|
73
|
+
"""Resolve the persistent data directory.
|
|
74
|
+
|
|
75
|
+
Precedence: TD_DATA env > cfg[paths|database].data_dir > ./data
|
|
76
|
+
Always creates the directory.
|
|
77
|
+
"""
|
|
78
|
+
val = _get_opt(cfg, "paths", "data_dir") or _get_opt(cfg, "database", "data_dir")
|
|
79
|
+
p = _resolve_dir("./data", "TD_DATA", val)
|
|
80
|
+
return ensure_dir(p)
|
|
81
|
+
|
|
82
|
+
def resolve_tmp_dir(cfg: Any = None) -> Path:
|
|
83
|
+
"""Resolve the temporary directory.
|
|
84
|
+
|
|
85
|
+
Precedence: TD_TMP env > cfg[paths|database].tmp_dir > ./tmp
|
|
86
|
+
Always creates the directory.
|
|
87
|
+
"""
|
|
88
|
+
val = _get_opt(cfg, "paths", "tmp_dir") or _get_opt(cfg, "database", "tmp_dir")
|
|
89
|
+
p = _resolve_dir("./tmp", "TD_TMP", val)
|
|
90
|
+
return ensure_dir(p)
|
|
91
|
+
|
|
92
|
+
def get_sqlite_db_path(cfg: Any = None) -> Path:
|
|
93
|
+
"""Return full path to the SQLite DB file (does not create the file).
|
|
94
|
+
|
|
95
|
+
Data dir is resolved via resolve_data_dir(cfg). Filename comes from:
|
|
96
|
+
cfg[sqlite].sqlite_filename or cfg[database].sqlite_filename or legacy default 'TradeDangerous.db'.
|
|
97
|
+
"""
|
|
98
|
+
data_dir = resolve_data_dir(cfg)
|
|
99
|
+
filename = (
|
|
100
|
+
_get_opt(cfg, "sqlite", "sqlite_filename")
|
|
101
|
+
or _get_opt(cfg, "database", "sqlite_filename")
|
|
102
|
+
or "TradeDangerous.db" # legacy default matches shipped tests/fixtures
|
|
103
|
+
)
|
|
104
|
+
return (data_dir / filename).resolve()
|
|
105
|
+
|
|
106
|
+
def resolve_db_config_path(default_name: str = "db_config.ini") -> Path:
|
|
107
|
+
"""Honor TD_DB_CONFIG env var for config file path, else default_name in CWD.
|
|
108
|
+
Does not read or validate contents; just returns a Path.
|
|
109
|
+
"""
|
|
110
|
+
cand = os.getenv("TD_DB_CONFIG") or default_name
|
|
111
|
+
p = Path(cand).expanduser()
|
|
112
|
+
return p if p.is_absolute() else (Path.cwd() / p)
|