evesde 0.1.0a1__tar.gz
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.
- evesde-0.1.0a1/LICENSE +21 -0
- evesde-0.1.0a1/PKG-INFO +256 -0
- evesde-0.1.0a1/README.md +213 -0
- evesde-0.1.0a1/pyproject.toml +91 -0
- evesde-0.1.0a1/setup.cfg +4 -0
- evesde-0.1.0a1/src/evesde/__init__.py +33 -0
- evesde-0.1.0a1/src/evesde/api/__init__.py +1 -0
- evesde-0.1.0a1/src/evesde/api/queries.py +804 -0
- evesde-0.1.0a1/src/evesde/api/sde.py +337 -0
- evesde-0.1.0a1/src/evesde/cli.py +227 -0
- evesde-0.1.0a1/src/evesde/config.py +151 -0
- evesde-0.1.0a1/src/evesde/etl/__init__.py +1 -0
- evesde-0.1.0a1/src/evesde/etl/loader.py +359 -0
- evesde-0.1.0a1/src/evesde/etl/marts.py +378 -0
- evesde-0.1.0a1/src/evesde/etl/source.py +217 -0
- evesde-0.1.0a1/src/evesde/etl/transform.py +264 -0
- evesde-0.1.0a1/src/evesde/etl/verify.py +237 -0
- evesde-0.1.0a1/src/evesde/py.typed +0 -0
- evesde-0.1.0a1/src/evesde/schema/__init__.py +1 -0
- evesde-0.1.0a1/src/evesde/schema/builder.py +211 -0
- evesde-0.1.0a1/src/evesde/schema/manifest.json +20508 -0
- evesde-0.1.0a1/src/evesde.egg-info/PKG-INFO +256 -0
- evesde-0.1.0a1/src/evesde.egg-info/SOURCES.txt +25 -0
- evesde-0.1.0a1/src/evesde.egg-info/dependency_links.txt +1 -0
- evesde-0.1.0a1/src/evesde.egg-info/entry_points.txt +2 -0
- evesde-0.1.0a1/src/evesde.egg-info/requires.txt +20 -0
- evesde-0.1.0a1/src/evesde.egg-info/top_level.txt +1 -0
evesde-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 evesde contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
evesde-0.1.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: evesde
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Библиотека для работы со статическими данными EVE Online (SDE)
|
|
5
|
+
Author: AlexMidRus
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Alexmidrus/Eve_SDE
|
|
8
|
+
Project-URL: Repository, https://github.com/Alexmidrus/Eve_SDE
|
|
9
|
+
Project-URL: Issues, https://github.com/Alexmidrus/Eve_SDE/issues
|
|
10
|
+
Keywords: eve-online,eve,sde,static-data-export,sqlalchemy
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Games/Entertainment
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
27
|
+
Requires-Dist: click>=8.1
|
|
28
|
+
Requires-Dist: httpx>=0.27
|
|
29
|
+
Provides-Extra: postgres
|
|
30
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
|
|
31
|
+
Provides-Extra: mysql
|
|
32
|
+
Requires-Dist: pymysql>=1.1; extra == "mysql"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
37
|
+
Requires-Dist: apscheduler>=3.10; extra == "dev"
|
|
38
|
+
Requires-Dist: celery>=5.3; extra == "dev"
|
|
39
|
+
Provides-Extra: release
|
|
40
|
+
Requires-Dist: build>=1.0; extra == "release"
|
|
41
|
+
Requires-Dist: twine>=5.0; extra == "release"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# evesde
|
|
45
|
+
|
|
46
|
+
Библиотека для работы со статическими данными EVE Online (SDE): загрузка
|
|
47
|
+
официального JSONL-дампа CCP в SQLite/PostgreSQL/MySQL/MariaDB и лаконичный
|
|
48
|
+
API для чтения игровых данных (~10 методов с богатыми параметрами вместо
|
|
49
|
+
десятков узких).
|
|
50
|
+
|
|
51
|
+
## English
|
|
52
|
+
|
|
53
|
+
`evesde` is a Python library for working with EVE Online's Static Data
|
|
54
|
+
Export (SDE): it loads CCP's official JSONL dump into SQLite/PostgreSQL/
|
|
55
|
+
MySQL/MariaDB and exposes a small, richly-parameterized query API (~10
|
|
56
|
+
methods) instead of dozens of narrow ones. Install with
|
|
57
|
+
`pip install evesde` (add `[postgres]`/`[mysql]` extras for those drivers),
|
|
58
|
+
then:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from evesde import SDE
|
|
62
|
+
|
|
63
|
+
sde = SDE("sqlite:///eve.db")
|
|
64
|
+
sde.update_if_needed() # downloads and loads SDE on first run
|
|
65
|
+
ship = sde.item("Rifter") # by name or type_id, any of the 8 SDE languages
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The rest of this README is in Russian; see the docstrings on `evesde.SDE`
|
|
69
|
+
for full per-method reference in English.
|
|
70
|
+
|
|
71
|
+
## Установка
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install evesde
|
|
75
|
+
# с драйвером для конкретной СУБД:
|
|
76
|
+
pip install "evesde[postgres]"
|
|
77
|
+
pip install "evesde[mysql]"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Быстрый старт
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from evesde import SDE
|
|
84
|
+
|
|
85
|
+
sde = SDE("sqlite:///eve.db")
|
|
86
|
+
sde.update_if_needed() # скачает и загрузит SDE при первом запуске
|
|
87
|
+
|
|
88
|
+
ship = sde.item("Rifter") # по имени или type_id, любой из 8 языков SDE
|
|
89
|
+
print(ship.name, ship.group_name, ship.category_name)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Если SDE уже загружен локальным каталогом (например, командой `evesde load`),
|
|
93
|
+
`update_if_needed()` можно не вызывать каждый раз — она сама проверяет версию
|
|
94
|
+
у CCP и ничего не делает, если данные уже актуальны.
|
|
95
|
+
|
|
96
|
+
## Публичный API
|
|
97
|
+
|
|
98
|
+
Все методы — на объекте `SDE`; принимают `lang="en"` (любой из 8 языков SDE:
|
|
99
|
+
`de/en/es/fr/ja/ko/ru/zh`) и возвращают лёгкие frozen dataclass'ы. Разрешение
|
|
100
|
+
"имя или id" единообразно: `int` — это `id`, `str` ищется точным совпадением
|
|
101
|
+
по локализованному имени, затем по подстроке; неоднозначность бросает
|
|
102
|
+
`SDEAmbiguousNameError` (со списком кандидатов в `.candidates`), отсутствие —
|
|
103
|
+
`SDENotFoundError`. Всё, что не покрыто API, доступно через `sde.engine`
|
|
104
|
+
(SQLAlchemy Core).
|
|
105
|
+
|
|
106
|
+
| Метод | Параметры | Возвращает | Пример |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| `item` | `name_or_id`, `lang` | `Item` | `sde.item("Rifter")` |
|
|
109
|
+
| `items` | `group`, `category`, `market_group`, `published`, `lang` | `list[Item]` | `sde.items(category="Ship", published=True)` |
|
|
110
|
+
| `search` | `name`, `lang`, `limit` | `list[Item]` | `sde.search("Rift")` |
|
|
111
|
+
| `stats` | `type_id_or_name`, `lang` | `TypeStats` | `sde.stats("Rifter").high_slots` |
|
|
112
|
+
| `dogma` | `type_id_or_name`, `lang` | `list[DogmaAttribute]` | `sde.dogma("Rifter")` |
|
|
113
|
+
| `system` | `name_or_id`, `lang` | `SolarSystem` | `sde.system("Jita").region_name` |
|
|
114
|
+
| `systems` | `region`, `min_security`, `max_security`, `lang` | `list[SolarSystem]` | `sde.systems(region="The Forge", min_security=0.5)` |
|
|
115
|
+
| `industry` | `blueprint` или `product`, `activity`, `lang` | `list[IndustryRecipe]` | `sde.industry(product="Rifter", activity="manufacturing")` |
|
|
116
|
+
| `agents` | `level`, `region`, `is_locator`, `corporation`, `lang` | `list[Agent]` | `sde.agents(level=4, region="The Forge", is_locator=True)` |
|
|
117
|
+
| `meta` | — | `Meta` | `sde.meta().build_number` |
|
|
118
|
+
| `update_if_needed` | `force`, `on_progress`, `on_complete` | `UpdateResult` | `sde.update_if_needed()` |
|
|
119
|
+
|
|
120
|
+
`industry()` нужен `blueprint` (сам чертёж) либо `product` (что он
|
|
121
|
+
производит); без `activity` возвращаются рецепты всех активностей чертежа
|
|
122
|
+
(`manufacturing`, `copying`, `invention`, `reaction`, `research_material`,
|
|
123
|
+
`research_time`), с `activity` — только она.
|
|
124
|
+
|
|
125
|
+
## Команды CLI
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
evesde --db sqlite:///eve.db load ./sde_dump # загрузить из локального каталога
|
|
129
|
+
evesde --db sqlite:///eve.db load --download # скачать и загрузить
|
|
130
|
+
evesde --db sqlite:///eve.db update # обновить, если вышел новый build
|
|
131
|
+
evesde --db sqlite:///eve.db verify # проверить целостность
|
|
132
|
+
evesde --db sqlite:///eve.db status # версия, число таблиц/строк, размер БД
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Вместо `--db <url>` можно задать переменную окружения `EVESDE_DB`. Коды
|
|
136
|
+
возврата: `0` — успех, `1` — ошибка выполнения, `2` — `verify` нашёл ошибки
|
|
137
|
+
(предупреждения код возврата не меняют).
|
|
138
|
+
|
|
139
|
+
## Расписание обновлений
|
|
140
|
+
|
|
141
|
+
Библиотека не содержит встроенного планировщика: `SDE.update_if_needed()`
|
|
142
|
+
(сверяет версию у CCP и, если вышел новый build, атомарно перезагружает БД)
|
|
143
|
+
и `evesde update` (то же самое из CLI) — это просто функции, которые ваше
|
|
144
|
+
приложение вызывает по расписанию любым удобным способом.
|
|
145
|
+
|
|
146
|
+
### cron + CLI
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# каждый день в 04:00 проверить версию у CCP и обновить БД при необходимости
|
|
150
|
+
0 4 * * * EVESDE_DB=sqlite:////srv/app/eve.db evesde update >> /var/log/evesde-update.log 2>&1
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### APScheduler
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from apscheduler.schedulers.blocking import BlockingScheduler
|
|
157
|
+
|
|
158
|
+
from evesde import SDE
|
|
159
|
+
|
|
160
|
+
sde = SDE("sqlite:///eve.db")
|
|
161
|
+
scheduler = BlockingScheduler()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@scheduler.scheduled_job("interval", hours=6)
|
|
165
|
+
def update_sde() -> None:
|
|
166
|
+
result = sde.update_if_needed(on_progress=print)
|
|
167
|
+
if result.updated:
|
|
168
|
+
print(f"SDE обновлён до build {result.new_build.build_number}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
scheduler.start()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Celery beat
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from celery import Celery
|
|
178
|
+
|
|
179
|
+
from evesde import SDE
|
|
180
|
+
|
|
181
|
+
app = Celery("myapp", broker="redis://localhost:6379/0")
|
|
182
|
+
sde = SDE("sqlite:///eve.db")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@app.task
|
|
186
|
+
def update_sde() -> None:
|
|
187
|
+
sde.update_if_needed()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
app.conf.beat_schedule = {
|
|
191
|
+
"update-sde-every-6-hours": {
|
|
192
|
+
"task": "myapp.tasks.update_sde",
|
|
193
|
+
"schedule": 6 * 60 * 60,
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Схема данных
|
|
199
|
+
|
|
200
|
+
Три слоя (полный список из 182 таблиц, колонки и связи — см.
|
|
201
|
+
`reference/SDE_schema_report.md`):
|
|
202
|
+
|
|
203
|
+
1. **raw-слой** (174 таблицы) — 1 исходный `.jsonl`-файл = 1 корневая
|
|
204
|
+
таблица + дочерние таблицы для вложенных массивов, без потерь и
|
|
205
|
+
нормализации справочников.
|
|
206
|
+
2. **Витрины** (8 таблиц: `dim_items`, `dim_universe`, `dim_agents`,
|
|
207
|
+
`type_common_stats`, `industry_activities/materials/products/skills`) —
|
|
208
|
+
денормализованные таблицы поверх raw-слоя, физически материализуются
|
|
209
|
+
заново после каждой загрузки (`INSERT ... SELECT`, не VIEW).
|
|
210
|
+
3. Публичный API читает в первую очередь витрины; raw-слой — для редких
|
|
211
|
+
полей и произвольных запросов через `sde.engine`.
|
|
212
|
+
|
|
213
|
+
## Ограничения и особенности данных
|
|
214
|
+
|
|
215
|
+
- `types.faction_id` — смешанная ссылка: часть значений (~10 из 33) на
|
|
216
|
+
самом деле ID NPC-корпораций, а не фракций. `dim_items.faction_name`
|
|
217
|
+
в этих случаях будет `None` — это ожидаемое поведение, а не баг.
|
|
218
|
+
- `agents_in_space.dungeon_id` не совпадает с `dungeons._key` — внешний
|
|
219
|
+
ключ на эту колонку сознательно не строится.
|
|
220
|
+
- Не у каждого предмета есть dogma-атрибуты (`typeDogma` заполнена не для
|
|
221
|
+
всех типов): `stats()` бросает `SDENotFoundError`, если характеристик
|
|
222
|
+
нет, `dogma()` в этом случае просто возвращает пустой список.
|
|
223
|
+
- Значения dogma-атрибутов в исходном SDE всегда `FLOAT`; там, где смысл
|
|
224
|
+
атрибута целочисленный (слоты, уровни скиллов), `type_common_stats`
|
|
225
|
+
приводит их к `INTEGER` (например, `stats(...).high_slots`).
|
|
226
|
+
|
|
227
|
+
## Как запускать тесты
|
|
228
|
+
|
|
229
|
+
Быстрый набор (SQLite in-memory, всегда) не требует ничего, кроме
|
|
230
|
+
dev-зависимостей:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
pip install -e ".[dev]"
|
|
234
|
+
pytest # включает dbms-тесты, только если СУБД доступна
|
|
235
|
+
pytest -m "not dbms" # явно только быстрый набор
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Интеграционные тесты на реальных PostgreSQL и MariaDB (маркер `dbms`)
|
|
239
|
+
поднимаются через `docker-compose.test.yml`:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
docker compose -f docker-compose.test.yml up -d
|
|
243
|
+
pip install -e ".[dev,postgres,mysql]"
|
|
244
|
+
|
|
245
|
+
export EVESDE_TEST_POSTGRES_URL="postgresql+psycopg://evesde:evesde@localhost:5432/evesde_test"
|
|
246
|
+
export EVESDE_TEST_MYSQL_URL="mysql+pymysql://evesde:evesde@localhost:3306/evesde_test"
|
|
247
|
+
pytest -m dbms
|
|
248
|
+
|
|
249
|
+
docker compose -f docker-compose.test.yml down -v
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Без этих переменных (или если СУБД недоступна) `dbms`-тесты аккуратно
|
|
253
|
+
пропускаются (skip), а не падают -- обычный `pytest` без docker всегда
|
|
254
|
+
зелёный. В CI (`.github/workflows/ci.yml`) есть отдельные джобы `lint`
|
|
255
|
+
(ruff + mypy), `test-sqlite` (матрица версий Python) и `test-dbms`
|
|
256
|
+
(PostgreSQL + MariaDB как сервисы).
|
evesde-0.1.0a1/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# evesde
|
|
2
|
+
|
|
3
|
+
Библиотека для работы со статическими данными EVE Online (SDE): загрузка
|
|
4
|
+
официального JSONL-дампа CCP в SQLite/PostgreSQL/MySQL/MariaDB и лаконичный
|
|
5
|
+
API для чтения игровых данных (~10 методов с богатыми параметрами вместо
|
|
6
|
+
десятков узких).
|
|
7
|
+
|
|
8
|
+
## English
|
|
9
|
+
|
|
10
|
+
`evesde` is a Python library for working with EVE Online's Static Data
|
|
11
|
+
Export (SDE): it loads CCP's official JSONL dump into SQLite/PostgreSQL/
|
|
12
|
+
MySQL/MariaDB and exposes a small, richly-parameterized query API (~10
|
|
13
|
+
methods) instead of dozens of narrow ones. Install with
|
|
14
|
+
`pip install evesde` (add `[postgres]`/`[mysql]` extras for those drivers),
|
|
15
|
+
then:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from evesde import SDE
|
|
19
|
+
|
|
20
|
+
sde = SDE("sqlite:///eve.db")
|
|
21
|
+
sde.update_if_needed() # downloads and loads SDE on first run
|
|
22
|
+
ship = sde.item("Rifter") # by name or type_id, any of the 8 SDE languages
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The rest of this README is in Russian; see the docstrings on `evesde.SDE`
|
|
26
|
+
for full per-method reference in English.
|
|
27
|
+
|
|
28
|
+
## Установка
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install evesde
|
|
32
|
+
# с драйвером для конкретной СУБД:
|
|
33
|
+
pip install "evesde[postgres]"
|
|
34
|
+
pip install "evesde[mysql]"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Быстрый старт
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from evesde import SDE
|
|
41
|
+
|
|
42
|
+
sde = SDE("sqlite:///eve.db")
|
|
43
|
+
sde.update_if_needed() # скачает и загрузит SDE при первом запуске
|
|
44
|
+
|
|
45
|
+
ship = sde.item("Rifter") # по имени или type_id, любой из 8 языков SDE
|
|
46
|
+
print(ship.name, ship.group_name, ship.category_name)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Если SDE уже загружен локальным каталогом (например, командой `evesde load`),
|
|
50
|
+
`update_if_needed()` можно не вызывать каждый раз — она сама проверяет версию
|
|
51
|
+
у CCP и ничего не делает, если данные уже актуальны.
|
|
52
|
+
|
|
53
|
+
## Публичный API
|
|
54
|
+
|
|
55
|
+
Все методы — на объекте `SDE`; принимают `lang="en"` (любой из 8 языков SDE:
|
|
56
|
+
`de/en/es/fr/ja/ko/ru/zh`) и возвращают лёгкие frozen dataclass'ы. Разрешение
|
|
57
|
+
"имя или id" единообразно: `int` — это `id`, `str` ищется точным совпадением
|
|
58
|
+
по локализованному имени, затем по подстроке; неоднозначность бросает
|
|
59
|
+
`SDEAmbiguousNameError` (со списком кандидатов в `.candidates`), отсутствие —
|
|
60
|
+
`SDENotFoundError`. Всё, что не покрыто API, доступно через `sde.engine`
|
|
61
|
+
(SQLAlchemy Core).
|
|
62
|
+
|
|
63
|
+
| Метод | Параметры | Возвращает | Пример |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| `item` | `name_or_id`, `lang` | `Item` | `sde.item("Rifter")` |
|
|
66
|
+
| `items` | `group`, `category`, `market_group`, `published`, `lang` | `list[Item]` | `sde.items(category="Ship", published=True)` |
|
|
67
|
+
| `search` | `name`, `lang`, `limit` | `list[Item]` | `sde.search("Rift")` |
|
|
68
|
+
| `stats` | `type_id_or_name`, `lang` | `TypeStats` | `sde.stats("Rifter").high_slots` |
|
|
69
|
+
| `dogma` | `type_id_or_name`, `lang` | `list[DogmaAttribute]` | `sde.dogma("Rifter")` |
|
|
70
|
+
| `system` | `name_or_id`, `lang` | `SolarSystem` | `sde.system("Jita").region_name` |
|
|
71
|
+
| `systems` | `region`, `min_security`, `max_security`, `lang` | `list[SolarSystem]` | `sde.systems(region="The Forge", min_security=0.5)` |
|
|
72
|
+
| `industry` | `blueprint` или `product`, `activity`, `lang` | `list[IndustryRecipe]` | `sde.industry(product="Rifter", activity="manufacturing")` |
|
|
73
|
+
| `agents` | `level`, `region`, `is_locator`, `corporation`, `lang` | `list[Agent]` | `sde.agents(level=4, region="The Forge", is_locator=True)` |
|
|
74
|
+
| `meta` | — | `Meta` | `sde.meta().build_number` |
|
|
75
|
+
| `update_if_needed` | `force`, `on_progress`, `on_complete` | `UpdateResult` | `sde.update_if_needed()` |
|
|
76
|
+
|
|
77
|
+
`industry()` нужен `blueprint` (сам чертёж) либо `product` (что он
|
|
78
|
+
производит); без `activity` возвращаются рецепты всех активностей чертежа
|
|
79
|
+
(`manufacturing`, `copying`, `invention`, `reaction`, `research_material`,
|
|
80
|
+
`research_time`), с `activity` — только она.
|
|
81
|
+
|
|
82
|
+
## Команды CLI
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
evesde --db sqlite:///eve.db load ./sde_dump # загрузить из локального каталога
|
|
86
|
+
evesde --db sqlite:///eve.db load --download # скачать и загрузить
|
|
87
|
+
evesde --db sqlite:///eve.db update # обновить, если вышел новый build
|
|
88
|
+
evesde --db sqlite:///eve.db verify # проверить целостность
|
|
89
|
+
evesde --db sqlite:///eve.db status # версия, число таблиц/строк, размер БД
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Вместо `--db <url>` можно задать переменную окружения `EVESDE_DB`. Коды
|
|
93
|
+
возврата: `0` — успех, `1` — ошибка выполнения, `2` — `verify` нашёл ошибки
|
|
94
|
+
(предупреждения код возврата не меняют).
|
|
95
|
+
|
|
96
|
+
## Расписание обновлений
|
|
97
|
+
|
|
98
|
+
Библиотека не содержит встроенного планировщика: `SDE.update_if_needed()`
|
|
99
|
+
(сверяет версию у CCP и, если вышел новый build, атомарно перезагружает БД)
|
|
100
|
+
и `evesde update` (то же самое из CLI) — это просто функции, которые ваше
|
|
101
|
+
приложение вызывает по расписанию любым удобным способом.
|
|
102
|
+
|
|
103
|
+
### cron + CLI
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# каждый день в 04:00 проверить версию у CCP и обновить БД при необходимости
|
|
107
|
+
0 4 * * * EVESDE_DB=sqlite:////srv/app/eve.db evesde update >> /var/log/evesde-update.log 2>&1
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### APScheduler
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from apscheduler.schedulers.blocking import BlockingScheduler
|
|
114
|
+
|
|
115
|
+
from evesde import SDE
|
|
116
|
+
|
|
117
|
+
sde = SDE("sqlite:///eve.db")
|
|
118
|
+
scheduler = BlockingScheduler()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@scheduler.scheduled_job("interval", hours=6)
|
|
122
|
+
def update_sde() -> None:
|
|
123
|
+
result = sde.update_if_needed(on_progress=print)
|
|
124
|
+
if result.updated:
|
|
125
|
+
print(f"SDE обновлён до build {result.new_build.build_number}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
scheduler.start()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Celery beat
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from celery import Celery
|
|
135
|
+
|
|
136
|
+
from evesde import SDE
|
|
137
|
+
|
|
138
|
+
app = Celery("myapp", broker="redis://localhost:6379/0")
|
|
139
|
+
sde = SDE("sqlite:///eve.db")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.task
|
|
143
|
+
def update_sde() -> None:
|
|
144
|
+
sde.update_if_needed()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
app.conf.beat_schedule = {
|
|
148
|
+
"update-sde-every-6-hours": {
|
|
149
|
+
"task": "myapp.tasks.update_sde",
|
|
150
|
+
"schedule": 6 * 60 * 60,
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Схема данных
|
|
156
|
+
|
|
157
|
+
Три слоя (полный список из 182 таблиц, колонки и связи — см.
|
|
158
|
+
`reference/SDE_schema_report.md`):
|
|
159
|
+
|
|
160
|
+
1. **raw-слой** (174 таблицы) — 1 исходный `.jsonl`-файл = 1 корневая
|
|
161
|
+
таблица + дочерние таблицы для вложенных массивов, без потерь и
|
|
162
|
+
нормализации справочников.
|
|
163
|
+
2. **Витрины** (8 таблиц: `dim_items`, `dim_universe`, `dim_agents`,
|
|
164
|
+
`type_common_stats`, `industry_activities/materials/products/skills`) —
|
|
165
|
+
денормализованные таблицы поверх raw-слоя, физически материализуются
|
|
166
|
+
заново после каждой загрузки (`INSERT ... SELECT`, не VIEW).
|
|
167
|
+
3. Публичный API читает в первую очередь витрины; raw-слой — для редких
|
|
168
|
+
полей и произвольных запросов через `sde.engine`.
|
|
169
|
+
|
|
170
|
+
## Ограничения и особенности данных
|
|
171
|
+
|
|
172
|
+
- `types.faction_id` — смешанная ссылка: часть значений (~10 из 33) на
|
|
173
|
+
самом деле ID NPC-корпораций, а не фракций. `dim_items.faction_name`
|
|
174
|
+
в этих случаях будет `None` — это ожидаемое поведение, а не баг.
|
|
175
|
+
- `agents_in_space.dungeon_id` не совпадает с `dungeons._key` — внешний
|
|
176
|
+
ключ на эту колонку сознательно не строится.
|
|
177
|
+
- Не у каждого предмета есть dogma-атрибуты (`typeDogma` заполнена не для
|
|
178
|
+
всех типов): `stats()` бросает `SDENotFoundError`, если характеристик
|
|
179
|
+
нет, `dogma()` в этом случае просто возвращает пустой список.
|
|
180
|
+
- Значения dogma-атрибутов в исходном SDE всегда `FLOAT`; там, где смысл
|
|
181
|
+
атрибута целочисленный (слоты, уровни скиллов), `type_common_stats`
|
|
182
|
+
приводит их к `INTEGER` (например, `stats(...).high_slots`).
|
|
183
|
+
|
|
184
|
+
## Как запускать тесты
|
|
185
|
+
|
|
186
|
+
Быстрый набор (SQLite in-memory, всегда) не требует ничего, кроме
|
|
187
|
+
dev-зависимостей:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install -e ".[dev]"
|
|
191
|
+
pytest # включает dbms-тесты, только если СУБД доступна
|
|
192
|
+
pytest -m "not dbms" # явно только быстрый набор
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Интеграционные тесты на реальных PostgreSQL и MariaDB (маркер `dbms`)
|
|
196
|
+
поднимаются через `docker-compose.test.yml`:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
docker compose -f docker-compose.test.yml up -d
|
|
200
|
+
pip install -e ".[dev,postgres,mysql]"
|
|
201
|
+
|
|
202
|
+
export EVESDE_TEST_POSTGRES_URL="postgresql+psycopg://evesde:evesde@localhost:5432/evesde_test"
|
|
203
|
+
export EVESDE_TEST_MYSQL_URL="mysql+pymysql://evesde:evesde@localhost:3306/evesde_test"
|
|
204
|
+
pytest -m dbms
|
|
205
|
+
|
|
206
|
+
docker compose -f docker-compose.test.yml down -v
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Без этих переменных (или если СУБД недоступна) `dbms`-тесты аккуратно
|
|
210
|
+
пропускаются (skip), а не падают -- обычный `pytest` без docker всегда
|
|
211
|
+
зелёный. В CI (`.github/workflows/ci.yml`) есть отдельные джобы `lint`
|
|
212
|
+
(ruff + mypy), `test-sqlite` (матрица версий Python) и `test-dbms`
|
|
213
|
+
(PostgreSQL + MariaDB как сервисы).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "evesde"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "Библиотека для работы со статическими данными EVE Online (SDE)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "AlexMidRus" }]
|
|
13
|
+
keywords = ["eve-online", "eve", "sde", "static-data-export", "sqlalchemy"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Games/Entertainment",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"sqlalchemy>=2.0",
|
|
30
|
+
"click>=8.1",
|
|
31
|
+
"httpx>=0.27",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/Alexmidrus/Eve_SDE"
|
|
36
|
+
Repository = "https://github.com/Alexmidrus/Eve_SDE"
|
|
37
|
+
Issues = "https://github.com/Alexmidrus/Eve_SDE/issues"
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
evesde = "evesde.cli:cli"
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
postgres = ["psycopg[binary]>=3.1"]
|
|
44
|
+
mysql = ["pymysql>=1.1"]
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest>=8.0",
|
|
47
|
+
"ruff>=0.5",
|
|
48
|
+
"mypy>=1.10",
|
|
49
|
+
# Только для теста, что примеры планировщиков из README «Расписание
|
|
50
|
+
# обновлений» реально работают -- не зависимости библиотеки,
|
|
51
|
+
# планировщик/очередь задач выбирает пользователь сам.
|
|
52
|
+
"apscheduler>=3.10",
|
|
53
|
+
"celery>=5.3",
|
|
54
|
+
]
|
|
55
|
+
# Инструменты сборки/публикации -- не нужны для работы или тестов библиотеки.
|
|
56
|
+
release = ["build>=1.0", "twine>=5.0"]
|
|
57
|
+
|
|
58
|
+
[tool.setuptools.packages.find]
|
|
59
|
+
where = ["src"]
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.package-data]
|
|
62
|
+
evesde = ["schema/manifest.json", "py.typed"]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 100
|
|
66
|
+
target-version = "py310"
|
|
67
|
+
src = ["src"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
71
|
+
|
|
72
|
+
[tool.ruff.lint.per-file-ignores]
|
|
73
|
+
# Фикстуры содержат однострочные SQL-стейтменты (ALTER TABLE ... FOREIGN KEY ...),
|
|
74
|
+
# которые нельзя перенести на несколько строк без изменения семантики теста.
|
|
75
|
+
"**/test_gen_manifest.py" = ["E501"]
|
|
76
|
+
# SQL витрин перенесён дословно из reference/eve_sde_full_schema.sql (см. etl/marts.py) --
|
|
77
|
+
# построчный CASE WHEN пивот на 67 колонок длиннее 100 символов сам по себе.
|
|
78
|
+
"**/marts.py" = ["E501"]
|
|
79
|
+
|
|
80
|
+
[tool.mypy]
|
|
81
|
+
python_version = "3.10"
|
|
82
|
+
strict = true
|
|
83
|
+
files = ["src"]
|
|
84
|
+
mypy_path = "src"
|
|
85
|
+
explicit_package_bases = true
|
|
86
|
+
|
|
87
|
+
[tool.pytest.ini_options]
|
|
88
|
+
testpaths = ["tests"]
|
|
89
|
+
markers = [
|
|
90
|
+
"dbms: интеграционные тесты, требующие реальных MySQL/PostgreSQL (docker-compose)",
|
|
91
|
+
]
|
evesde-0.1.0a1/setup.cfg
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""evesde — библиотека для работы со статическими данными EVE Online (SDE)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from evesde.api.sde import (
|
|
6
|
+
SDE,
|
|
7
|
+
Agent,
|
|
8
|
+
DogmaAttribute,
|
|
9
|
+
IndustryRecipe,
|
|
10
|
+
Item,
|
|
11
|
+
Meta,
|
|
12
|
+
SDEAmbiguousNameError,
|
|
13
|
+
SDENotFoundError,
|
|
14
|
+
SolarSystem,
|
|
15
|
+
TypeStats,
|
|
16
|
+
UpdateResult,
|
|
17
|
+
)
|
|
18
|
+
from evesde.config import SDEConfig
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"SDE",
|
|
22
|
+
"Agent",
|
|
23
|
+
"DogmaAttribute",
|
|
24
|
+
"IndustryRecipe",
|
|
25
|
+
"Item",
|
|
26
|
+
"Meta",
|
|
27
|
+
"SDEAmbiguousNameError",
|
|
28
|
+
"SDENotFoundError",
|
|
29
|
+
"SDEConfig",
|
|
30
|
+
"SolarSystem",
|
|
31
|
+
"TypeStats",
|
|
32
|
+
"UpdateResult",
|
|
33
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Публичный API запросов evesde."""
|