aa-ledger 1.0.3__py3-none-any.whl → 2.0.0__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.
Files changed (280) hide show
  1. {aa_ledger-1.0.3.dist-info → aa_ledger-2.0.0.dist-info}/METADATA +6 -6
  2. aa_ledger-2.0.0.dist-info/RECORD +267 -0
  3. {aa_ledger-1.0.3.dist-info → aa_ledger-2.0.0.dist-info}/WHEEL +1 -1
  4. ledger/__init__.py +2 -2
  5. ledger/admin.py +23 -18
  6. ledger/api/__init__.py +23 -7
  7. ledger/api/{ledger/admin.py → admin.py} +25 -31
  8. ledger/api/alliance.py +755 -0
  9. ledger/api/character.py +786 -0
  10. ledger/api/corporation.py +1141 -0
  11. ledger/api/{helpers.py → helpers/core.py} +33 -33
  12. ledger/api/helpers/icons.py +372 -0
  13. ledger/api/helpers/planetary_helper.py +354 -0
  14. ledger/api/planetary.py +354 -0
  15. ledger/api/schema.py +240 -15
  16. ledger/app_settings.py +18 -26
  17. ledger/auth_hooks.py +2 -2
  18. ledger/constants.py +50 -177
  19. ledger/decorators.py +2 -46
  20. ledger/forms.py +133 -39
  21. ledger/helpers/billboard.py +194 -144
  22. ledger/helpers/cache.py +105 -0
  23. ledger/helpers/discord.py +2 -4
  24. ledger/helpers/eveonline.py +160 -0
  25. ledger/helpers/ledger_data.py +23 -0
  26. ledger/helpers/ref_type.py +53 -78
  27. ledger/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  28. ledger/locale/cs_CZ/LC_MESSAGES/django.po +349 -193
  29. ledger/locale/de/LC_MESSAGES/django.mo +0 -0
  30. ledger/locale/de/LC_MESSAGES/django.po +528 -379
  31. ledger/locale/django.pot +717 -553
  32. ledger/locale/es/LC_MESSAGES/django.mo +0 -0
  33. ledger/locale/es/LC_MESSAGES/django.po +349 -194
  34. ledger/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  35. ledger/locale/fr_FR/LC_MESSAGES/django.po +349 -193
  36. ledger/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  37. ledger/locale/it_IT/LC_MESSAGES/django.po +349 -193
  38. ledger/locale/ja/LC_MESSAGES/django.mo +0 -0
  39. ledger/locale/ja/LC_MESSAGES/django.po +348 -193
  40. ledger/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  41. ledger/locale/ko_KR/LC_MESSAGES/django.po +349 -193
  42. ledger/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  43. ledger/locale/nl_NL/LC_MESSAGES/django.po +349 -193
  44. ledger/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  45. ledger/locale/pl_PL/LC_MESSAGES/django.po +350 -193
  46. ledger/locale/ru/LC_MESSAGES/django.mo +0 -0
  47. ledger/locale/ru/LC_MESSAGES/django.po +348 -193
  48. ledger/locale/sk/LC_MESSAGES/django.mo +0 -0
  49. ledger/locale/sk/LC_MESSAGES/django.po +348 -193
  50. ledger/locale/uk/LC_MESSAGES/django.mo +0 -0
  51. ledger/locale/uk/LC_MESSAGES/django.po +348 -193
  52. ledger/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  53. ledger/locale/zh_Hans/LC_MESSAGES/django.po +348 -193
  54. ledger/managers/character_audit_manager.py +28 -20
  55. ledger/managers/character_journal_manager.py +187 -358
  56. ledger/managers/character_mining_manager.py +64 -30
  57. ledger/managers/character_planetary_manager.py +185 -138
  58. ledger/managers/corporation_audit_manager.py +36 -27
  59. ledger/managers/corporation_journal_manager.py +94 -57
  60. ledger/managers/general_manager.py +12 -8
  61. ledger/migrations/0018_remove_characterplanet_ledger_char_planet__58a5b6_idx_and_more.py +44 -0
  62. ledger/migrations/0019_rename_characteraudit_characterowner_and_more.py +48 -0
  63. ledger/models/__init__.py +5 -11
  64. ledger/models/characteraudit.py +101 -109
  65. ledger/models/corporationaudit.py +94 -49
  66. ledger/models/general.py +105 -211
  67. ledger/models/helpers/update_manager.py +302 -0
  68. ledger/models/planetary.py +60 -205
  69. ledger/providers.py +101 -0
  70. ledger/static/ledger/css/{ledger.css → aa-ledger.css} +54 -28
  71. ledger/static/ledger/js/aa-ledger.js +124 -0
  72. ledger/static/ledger/js/charts.js +25 -1
  73. ledger/static/ledger/js/view-alliance-ledger.js +383 -0
  74. ledger/static/ledger/js/view-character-ledger.js +388 -0
  75. ledger/static/ledger/js/view-corporation-ledger.js +402 -0
  76. ledger/static/ledger/js/view-planetary.js +492 -0
  77. ledger/static/ledger/libs/amCharts/5.14.4/js/flow.js +2 -0
  78. ledger/static/ledger/libs/amCharts/5.14.4/js/index.js +2 -0
  79. ledger/static/ledger/libs/amCharts/5.14.4/js/percent.js +2 -0
  80. ledger/static/ledger/libs/amCharts/5.14.4/js/themes/Animated.js +2 -0
  81. ledger/static/ledger/libs/amCharts/5.14.4/js/themes/Dark.js +2 -0
  82. ledger/static/ledger/libs/amCharts/5.14.4/js/xy.js +2 -0
  83. ledger/static/ledger/libs/datatables/2.3.5/css/dataTables.bootstrap5.css +610 -0
  84. ledger/static/ledger/libs/datatables/2.3.5/js/dataTables.bootstrap5.js +122 -0
  85. ledger/static/ledger/libs/datatables/2.3.5/js/dataTables.js +14127 -0
  86. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/css/columnControl.bootstrap5.css +516 -0
  87. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/css/columnControl.dataTables.css +529 -0
  88. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/js/columnControl.bootstrap5.js +73 -0
  89. ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/js/dataTables.columnControl.js +3090 -0
  90. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/css/fixedHeader.bootstrap5.css +20 -0
  91. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/js/dataTables.fixedHeader.js +1203 -0
  92. ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/js/fixedHeader.bootstrap5.js +59 -0
  93. ledger/tasks.py +157 -146
  94. ledger/templates/ledger/base.html +59 -21
  95. ledger/templates/ledger/bundles/aa-ledger-css.html +3 -0
  96. ledger/templates/ledger/bundles/aa-ledger-js.html +3 -0
  97. ledger/templates/ledger/bundles/view-alliance-ledger-js.html +14 -0
  98. ledger/templates/ledger/bundles/view-character-ledger-js.html +15 -0
  99. ledger/templates/ledger/bundles/view-character-planetary-css.html +3 -0
  100. ledger/templates/ledger/bundles/view-character-planetary-js.html +4 -0
  101. ledger/templates/ledger/bundles/view-corporation-ledger-js.html +15 -0
  102. ledger/templates/ledger/partials/modal/confirm.html +0 -1
  103. ledger/templates/ledger/partials/modal/request-accept-delete-alliance.html +38 -0
  104. ledger/templates/ledger/partials/modal/request-accept-delete-character.html +38 -0
  105. ledger/templates/ledger/partials/modal/request-accept-delete-corporation.html +38 -0
  106. ledger/templates/ledger/partials/modal/request-accept-switch-notification.html +38 -0
  107. ledger/templates/ledger/partials/modal/request-view-alliance-details.html +26 -0
  108. ledger/templates/ledger/partials/modal/request-view-character-details.html +26 -0
  109. ledger/templates/ledger/partials/modal/request-view-corporation-details.html +26 -0
  110. ledger/templates/ledger/partials/modal/request-view-extractor.html +32 -0
  111. ledger/templates/ledger/partials/modal/request-view-factory.html +31 -0
  112. ledger/templates/ledger/partials/{menu → navigation}/administration.html +8 -0
  113. ledger/templates/ledger/partials/{menu → navigation}/navigation.html +2 -2
  114. ledger/templates/ledger/partials/{administration → view-alliance-administration}/alliance_corporations.html +3 -3
  115. ledger/templates/ledger/partials/view-alliance-administration/dashboard.html +81 -0
  116. ledger/templates/ledger/partials/view-alliance-ledger/alliance-billboard.html +25 -0
  117. ledger/templates/ledger/partials/view-alliance-ledger/alliance-ledger-details.html +21 -0
  118. ledger/templates/ledger/partials/view-alliance-ledger/alliance-table.html +24 -0
  119. ledger/templates/ledger/partials/view-alliance-ledger/information/daily.html +18 -0
  120. ledger/templates/ledger/partials/view-alliance-ledger/information/hourly.html +18 -0
  121. ledger/templates/ledger/partials/view-alliance-ledger/information/summary.html +19 -0
  122. ledger/templates/ledger/partials/{administration → view-character-administration}/character.html +1 -9
  123. ledger/templates/ledger/partials/{administration → view-character-administration}/dashboard.html +0 -34
  124. ledger/templates/ledger/partials/view-character-ledger/character-billboard.html +25 -0
  125. ledger/templates/ledger/partials/view-character-ledger/character-ledger-details.html +21 -0
  126. ledger/templates/ledger/partials/view-character-ledger/character-table.html +25 -0
  127. ledger/templates/ledger/partials/view-character-ledger/information/daily.html +18 -0
  128. ledger/templates/ledger/partials/view-character-ledger/information/hourly.html +18 -0
  129. ledger/templates/ledger/partials/view-character-ledger/information/summary.html +19 -0
  130. ledger/templates/ledger/partials/view-character-planetary/extractor-table.html +24 -0
  131. ledger/templates/ledger/partials/view-character-planetary/factory-table.html +24 -0
  132. ledger/templates/ledger/partials/view-character-planetary/planetary-table.html +22 -0
  133. ledger/templates/ledger/partials/view-character-planetary/storage-table.html +23 -0
  134. ledger/templates/ledger/partials/{administration → view-corporation-administration}/corporation.html +5 -13
  135. ledger/templates/ledger/partials/{administration → view-corporation-administration}/corporation_characters.html +1 -1
  136. ledger/templates/ledger/partials/view-corporation-administration/dashboard.html +81 -0
  137. ledger/templates/ledger/partials/view-corporation-ledger/corporation-billboard.html +25 -0
  138. ledger/templates/ledger/partials/view-corporation-ledger/corporation-ledger-details.html +21 -0
  139. ledger/templates/ledger/partials/view-corporation-ledger/corporation-table.html +26 -0
  140. ledger/templates/ledger/partials/view-corporation-ledger/information/daily.html +18 -0
  141. ledger/templates/ledger/partials/view-corporation-ledger/information/hourly.html +18 -0
  142. ledger/templates/ledger/partials/view-corporation-ledger/information/summary.html +19 -0
  143. ledger/templates/ledger/view-administration.html +62 -0
  144. ledger/templates/ledger/view-alliance-administration.html +49 -0
  145. ledger/templates/ledger/view-alliance-ledger.html +72 -0
  146. ledger/templates/ledger/view-alliance-overview.html +131 -0
  147. ledger/templates/ledger/view-character-administration.html +42 -0
  148. ledger/templates/ledger/view-character-ledger.html +73 -0
  149. ledger/templates/ledger/view-character-overview.html +135 -0
  150. ledger/templates/ledger/view-character-planetary-overview.html +135 -0
  151. ledger/templates/ledger/view-character-planetary.html +73 -0
  152. ledger/templates/ledger/view-corporation-administration.html +42 -0
  153. ledger/templates/ledger/view-corporation-ledger.html +73 -0
  154. ledger/templates/ledger/view-corporation-overview.html +131 -0
  155. ledger/templatetags/ledger.py +3 -5
  156. ledger/tests/__init__.py +187 -0
  157. ledger/tests/test_admin.py +164 -68
  158. ledger/tests/test_auth_hook.py +31 -13
  159. ledger/tests/test_decarators.py +14 -79
  160. ledger/tests/test_discord_installed.py +0 -1
  161. ledger/tests/test_helpers/test_ledger_data.py +19 -0
  162. ledger/tests/test_managers/test_character_audit_manager.py +111 -69
  163. ledger/tests/test_managers/test_character_journal_manager.py +48 -208
  164. ledger/tests/test_managers/test_character_mining_manager.py +37 -16
  165. ledger/tests/test_managers/test_corporation_division_manager.py +66 -28
  166. ledger/tests/test_managers/test_corporation_journal_manager.py +39 -42
  167. ledger/tests/test_managers/test_general_manager.py +78 -18
  168. ledger/tests/test_managers/test_planetary_manager.py +73 -32
  169. ledger/tests/test_models/test_characteraudit.py +58 -74
  170. ledger/tests/test_models/test_characterminingledger.py +20 -26
  171. ledger/tests/test_models/test_characterwalletjournal.py +10 -33
  172. ledger/tests/test_models/test_corporationaudit.py +41 -35
  173. ledger/tests/test_models/test_corporationwalletjournal.py +35 -32
  174. ledger/tests/test_models/test_general.py +44 -11
  175. ledger/tests/test_models/test_planetary.py +14 -80
  176. ledger/tests/test_templatetags.py +2 -7
  177. ledger/tests/test_views/corporation/test_add_corp.py +16 -35
  178. ledger/tests/test_views/corporation/test_delete_corporation.py +66 -42
  179. ledger/tests/test_views/test_access.py +512 -545
  180. ledger/tests/test_views/test_add_ally.py +57 -46
  181. ledger/tests/test_views/test_add_char.py +21 -33
  182. ledger/tests/test_views/test_delete_character.py +24 -21
  183. ledger/tests/testdata/README_ESI_STUB.md +430 -0
  184. ledger/tests/testdata/esi_stub_openapi.py +511 -0
  185. ledger/tests/testdata/integrations/__init__.py +0 -0
  186. ledger/tests/testdata/{load_eveuniverse.py → integrations/eveuniverse.py} +0 -1
  187. ledger/tests/testdata/integrations/planetary.py +13 -0
  188. ledger/tests/testdata/json/factory.json +281 -0
  189. ledger/tests/testdata/json/inactive.json +281 -0
  190. ledger/tests/testdata/json/pins.json +175 -272
  191. ledger/tests/testdata/json/route.json +95 -528
  192. ledger/tests/testdata/test_esi_stub.py +468 -0
  193. ledger/tests/testdata/utils.py +601 -0
  194. ledger/thirdparty/charlink_hook.py +60 -30
  195. ledger/urls.py +0 -135
  196. ledger/views/alliance/add_ally.py +2 -4
  197. ledger/views/alliance/alliance_ledger.py +64 -147
  198. ledger/views/character/add_char.py +8 -10
  199. ledger/views/character/character_ledger.py +60 -126
  200. ledger/views/character/planetary.py +5 -98
  201. ledger/views/corporation/add_corp.py +10 -12
  202. ledger/views/corporation/corporation_ledger.py +65 -327
  203. ledger/views/index.py +92 -30
  204. aa_ledger-1.0.3.dist-info/RECORD +0 -236
  205. ledger/api/api_helper/planetary_helper.py +0 -107
  206. ledger/api/ledger/__init__.py +0 -7
  207. ledger/api/ledger/planetary.py +0 -231
  208. ledger/helpers/alliance.py +0 -317
  209. ledger/helpers/character.py +0 -251
  210. ledger/helpers/core.py +0 -665
  211. ledger/helpers/corporation.py +0 -427
  212. ledger/helpers/data_exporter.py +0 -452
  213. ledger/static/ledger/js/planetary-confirm.js +0 -66
  214. ledger/static/ledger/js/planetary.js +0 -143
  215. ledger/templates/ledger/admin.html +0 -43
  216. ledger/templates/ledger/allyledger/admin/alliance_administration.html +0 -46
  217. ledger/templates/ledger/allyledger/admin/alliance_overview.html +0 -108
  218. ledger/templates/ledger/allyledger/alliance_ledger.html +0 -86
  219. ledger/templates/ledger/bundles/character-ledger-bundles.html +0 -66
  220. ledger/templates/ledger/bundles/corporation-ledger-bundles.html +0 -75
  221. ledger/templates/ledger/bundles/ledger-bundles.html +0 -23
  222. ledger/templates/ledger/bundles/ledger-css.html +0 -3
  223. ledger/templates/ledger/bundles/planetary-bundles.html +0 -50
  224. ledger/templates/ledger/bundles/table-css.html +0 -3
  225. ledger/templates/ledger/charledger/admin/character_administration.html +0 -39
  226. ledger/templates/ledger/charledger/admin/character_overview.html +0 -106
  227. ledger/templates/ledger/charledger/character_ledger.html +0 -94
  228. ledger/templates/ledger/charledger/planetary/admin/planetary_overview.html +0 -123
  229. ledger/templates/ledger/charledger/planetary/planetary_ledger.html +0 -54
  230. ledger/templates/ledger/corpledger/admin/corporation_administration.html +0 -39
  231. ledger/templates/ledger/corpledger/admin/corporation_overview.html +0 -108
  232. ledger/templates/ledger/corpledger/corporation_ledger.html +0 -129
  233. ledger/templates/ledger/data-export.html +0 -78
  234. ledger/templates/ledger/error.html +0 -31
  235. ledger/templates/ledger/partials/form/error-message.html +0 -1
  236. ledger/templates/ledger/partials/information/daily.html +0 -56
  237. ledger/templates/ledger/partials/information/day.html +0 -48
  238. ledger/templates/ledger/partials/information/error.html +0 -8
  239. ledger/templates/ledger/partials/information/hourly.html +0 -53
  240. ledger/templates/ledger/partials/information/summary.html +0 -88
  241. ledger/templates/ledger/partials/information/view_character_content.html +0 -35
  242. ledger/templates/ledger/partials/modal/switchalarm_confirm.html +0 -39
  243. ledger/templates/ledger/partials/modal/view_extractor.html +0 -48
  244. ledger/templates/ledger/partials/modal/view_factory.html +0 -123
  245. ledger/templates/ledger/partials/table/char-ledger.html +0 -85
  246. ledger/templates/ledger/partials/table/corp-ledger.html +0 -66
  247. ledger/templates/ledger/partials/table/planetary.html +0 -18
  248. ledger/templates/ledger/partials/thirdparty/billboard.html +0 -22
  249. ledger/templates/ledger/partials/view/card.html +0 -160
  250. ledger/templates/ledger/permission.html +0 -2
  251. ledger/tests/test_helpers/test_billboard.py +0 -11
  252. ledger/tests/test_helpers/test_data_exporter.py +0 -207
  253. ledger/tests/test_tasks.py +0 -282
  254. ledger/tests/test_view_helpers/test_core.py +0 -47
  255. ledger/tests/test_views/corporation/test_corporation.py +0 -267
  256. ledger/tests/test_views/test_planetary.py +0 -137
  257. ledger/tests/testdata/esi_stub.py +0 -109
  258. ledger/tests/testdata/esi_stub_migration.py +0 -80
  259. ledger/tests/testdata/generate_characteraudit.py +0 -106
  260. ledger/tests/testdata/generate_corporationaudit.py +0 -74
  261. ledger/tests/testdata/generate_events.py +0 -31
  262. ledger/tests/testdata/generate_miningledger.py +0 -13
  263. ledger/tests/testdata/generate_planets.py +0 -48
  264. ledger/tests/testdata/generate_walletjournal.py +0 -42
  265. ledger/tests/testdata/json/czarno-pins.json +0 -240
  266. ledger/tests/testdata/json/czarno-routes.json +0 -165
  267. ledger/tests/testdata/json/pins2.json +0 -538
  268. {aa_ledger-1.0.3.dist-info → aa_ledger-2.0.0.dist-info}/licenses/LICENSE +0 -0
  269. /ledger/{tests/test_view_helpers → api/helpers}/__init__.py +0 -0
  270. /ledger/templates/ledger/bundles/{ally-administration-bundles.html → view-alliance-administration-js.html} +0 -0
  271. /ledger/templates/ledger/bundles/{char-administration-bundles.html → view-character-administration-js.html} +0 -0
  272. /ledger/templates/ledger/bundles/{corp-administration-bundles.html → view-corporation-administration-js.html} +0 -0
  273. /ledger/templates/ledger/partials/{administration → view-alliance-administration}/alliance.html +0 -0
  274. /ledger/tests/testdata/{esi.json → esi_test_data.json} +0 -0
  275. /ledger/tests/testdata/{allianceauth.json → integrations/allianceauth.json} +0 -0
  276. /ledger/tests/testdata/{load_allianceauth.py → integrations/allianceauth.py} +0 -0
  277. /ledger/tests/testdata/{eveentity.json → integrations/eveentity.json} +0 -0
  278. /ledger/tests/testdata/{load_eveentity.py → integrations/eveentity.py} +0 -0
  279. /ledger/tests/testdata/{eveuniverse.json → integrations/eveuniverse.json} +0 -0
  280. /ledger/tests/testdata/{planetary.json → integrations/planetary.json} +0 -0
@@ -0,0 +1,511 @@
1
+ """
2
+ ESI OpenAPI Client Stub for Testing
3
+
4
+ This module provides a stub implementation for ESI OpenAPI clients that can be used
5
+ in tests to return predefined test data without making actual API calls.
6
+ """
7
+
8
+ # Standard Library
9
+ import json
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ # Third Party
15
+ from pydantic import BaseModel, create_model
16
+
17
+
18
+ def _to_pydantic_model_instance(name: str, data: Any) -> Any:
19
+ """
20
+ Recursively convert dicts/lists to Pydantic model instances.
21
+ """
22
+ # Lists -> convert each item
23
+ if isinstance(data, list):
24
+ return [_to_pydantic_model_instance(name + "Item", v) for v in data]
25
+
26
+ # Helper: try to parse ISO datetime strings into datetime objects
27
+ def _try_parse_datetime(value: Any) -> Any:
28
+ if not isinstance(value, str):
29
+ return value
30
+ try:
31
+ v = value
32
+ # Accept trailing Z as UTC
33
+ if v.endswith("Z"):
34
+ v = v[:-1] + "+00:00"
35
+ return datetime.fromisoformat(v)
36
+ except Exception:
37
+ return value
38
+
39
+ # Dicts -> create a transient pydantic model class and instantiate it
40
+ if isinstance(data, dict):
41
+ fields: dict[str, tuple[type, Any]] = {}
42
+ values: dict[str, Any] = {}
43
+ for k, v in data.items():
44
+ # Use Any for field type; instantiate nested models recursively
45
+ fields[k] = (Any, ...)
46
+ # Recursively convert nested structures
47
+ val = _to_pydantic_model_instance(name + k.capitalize(), v)
48
+ # Try to convert ISO datetime-like strings to datetime objects
49
+ if isinstance(val, str):
50
+ val = _try_parse_datetime(val)
51
+ values[k] = val
52
+
53
+ Model = create_model(name, **fields, __base__=BaseModel)
54
+ return Model(**values)
55
+
56
+ # Primitives -> return as-is
57
+ return data
58
+
59
+
60
+ def _select_method_data(method_data: Any, kwargs: dict, endpoint=None):
61
+ """Select appropriate test data entry from a mapping using endpoint param names.
62
+
63
+ - If method_data is a dict and endpoint.param_names are provided, try to match
64
+ kwargs values (as str or int) to the dict keys. If found, return that value.
65
+ - If only a single entry exists in the dict, return its value as a sensible default.
66
+ - Otherwise return method_data unchanged.
67
+ """
68
+ if not endpoint or not isinstance(method_data, dict):
69
+ return method_data
70
+
71
+ # Drill down through param names in order, attempting to match kwargs
72
+ current = method_data
73
+ matched_any = False
74
+ for param_name in endpoint.param_names:
75
+ if not isinstance(current, dict):
76
+ break
77
+ if param_name in kwargs and kwargs[param_name] is not None:
78
+ key = str(kwargs[param_name])
79
+ # direct string key
80
+ if key in current:
81
+ current = current[key]
82
+ matched_any = True
83
+ continue
84
+ # try integer key match
85
+ try:
86
+ int_key = int(kwargs[param_name])
87
+ except Exception:
88
+ int_key = None
89
+ if int_key is not None and int_key in current:
90
+ current = current[int_key]
91
+ matched_any = True
92
+ continue
93
+ # if we couldn't match this param, stop drilling
94
+ break
95
+
96
+ if matched_any:
97
+ return current
98
+
99
+ # Fallback for POST-style methods where the body contains ids (common key 'body')
100
+ if (
101
+ "body" in kwargs
102
+ and kwargs["body"] is not None
103
+ and isinstance(kwargs["body"], (list, tuple))
104
+ ):
105
+ key = str(kwargs["body"])
106
+ if key in method_data:
107
+ return method_data[key]
108
+
109
+ # Fallback: if single-entry dict, return its value
110
+ if isinstance(method_data, dict) and len(method_data) == 1:
111
+ return list(method_data.values())[0]
112
+
113
+ return method_data
114
+
115
+
116
+ class MockResponse:
117
+ """
118
+ Mock HTTP response object for testing.
119
+
120
+ Mimics the response object returned by ESI when return_response=True.
121
+ """
122
+
123
+ def __init__(self, status_code: int = 200, headers: dict | None = None):
124
+ """
125
+ Initialize mock response.
126
+
127
+ Attributes:
128
+ status_code (int): HTTP status code
129
+ headers (dict | None): Response headers
130
+ """
131
+ self.status_code = status_code
132
+ self.headers = headers or {"X-Pages": 1}
133
+ self.text = ""
134
+ self.content = b""
135
+
136
+
137
+ class EsiEndpoint:
138
+ """
139
+ Definition of an ESI endpoint for stub configuration.
140
+
141
+ Defines which endpoints should be stubbed and with what side effects.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ category: str,
147
+ method: str,
148
+ param_names: str | tuple[str, ...],
149
+ side_effect: Exception | None = None,
150
+ ):
151
+ """
152
+ Initialize an ESI endpoint definition.
153
+
154
+ Attributes:
155
+ category (str): ESI category name (e.g., "Character", "Skills")
156
+ method (str): ESI method name (e.g., "GetCharactersCharacterIdSkills")
157
+ param_names (str | tuple[str, ...]): Parameter name(s) used to look up test data
158
+ side_effect (Exception | None): Optional exception to raise when this endpoint is called
159
+ """
160
+ self.category = category
161
+ self.method = method
162
+ # Allow tests to pass multiple param names as two positional strings
163
+ if isinstance(param_names, tuple):
164
+ params = list(param_names)
165
+ else:
166
+ params = [param_names]
167
+
168
+ # If side_effect is a string, tests likely passed a second param name
169
+ if isinstance(side_effect, str):
170
+ params.append(side_effect)
171
+ self.side_effect = None
172
+ else:
173
+ self.side_effect = side_effect
174
+
175
+ self.param_names = tuple(params)
176
+
177
+ def __repr__(self):
178
+ return f"EsiEndpoint({self.category}.{self.method})"
179
+
180
+
181
+ class EsiOperationStub:
182
+ """
183
+ Stub for ESI operation that mimics the behavior of openapi_clients operations.
184
+
185
+ This class simulates the result() and results() methods that are called on
186
+ ESI operations in the actual implementation.
187
+
188
+ If a side_effect is configured, calling result() or results() will raise that exception
189
+ instead of returning test data.
190
+ """
191
+
192
+ def __init__(self, test_data: Any, side_effect: Exception | None = None):
193
+ """
194
+ Initialize the operation stub with test data or side effect.
195
+
196
+ Attributes:
197
+ test_data (Any): The data to return when result() or results() is called
198
+ side_effect (Exception | None): Exception to raise when result() or results() is called
199
+ """
200
+ self._test_data = test_data
201
+ self._side_effect = side_effect
202
+
203
+ def result(
204
+ self,
205
+ use_etag: bool = True, # not implemented yet
206
+ return_response: bool = False,
207
+ force_refresh: bool = False, # not implemented yet
208
+ use_cache: bool = True, # not implemented yet
209
+ **kwargs,
210
+ ) -> Any:
211
+ """
212
+ Simulate the result() method of an ESI operation.
213
+
214
+ Returns a single result (not a list) as an object with attributes.
215
+ When return_response=True, returns tuple of (data, response).
216
+
217
+ If a side_effect was configured, raises that exception instead.
218
+
219
+ Args:
220
+ use_etag (bool) Whether to use ETag (ignored in stub)
221
+ return_response (bool): Whether to return response object
222
+ force_refresh (bool): Whether to force refresh (ignored in stub)
223
+ use_cache (bool): Whether to use cache (ignored in stub)
224
+ Returns:
225
+ Any: Test data as object, or tuple of (data, response) if return_response=True
226
+ Raises:
227
+ Exception: if side_effect was configured
228
+ """
229
+ # If side_effect is configured, raise it
230
+ if self._side_effect is not None:
231
+ # Support both exception instances and lists of exceptions/values
232
+ if isinstance(self._side_effect, list):
233
+ # Pop from list for sequential side effects
234
+ if self._side_effect:
235
+ effect = self._side_effect.pop(0)
236
+ if isinstance(effect, Exception):
237
+ raise effect
238
+ # If not an exception, return it as data
239
+ return (
240
+ _to_pydantic_model_instance("SideEffect", effect)
241
+ if not return_response
242
+ else (
243
+ _to_pydantic_model_instance("SideEffect", effect),
244
+ MockResponse(),
245
+ )
246
+ )
247
+ elif isinstance(self._side_effect, Exception):
248
+ raise self._side_effect
249
+
250
+ # Convert dict to Pydantic model instance to mimic OpenAPI 3 behavior
251
+ data = _to_pydantic_model_instance("Result", self._test_data)
252
+
253
+ if return_response:
254
+ # Return tuple of (data, response)
255
+ response = MockResponse()
256
+ return (data, response)
257
+
258
+ return data
259
+
260
+ def results(
261
+ self,
262
+ use_etag: bool = True, # not implemented yet
263
+ return_response: bool = False,
264
+ force_refresh: bool = False, # not implemented yet
265
+ use_cache: bool = True, # not implemented yet
266
+ **kwargs,
267
+ ) -> list[Any]:
268
+ """
269
+ Simulate the results() method of an ESI operation.
270
+
271
+ Returns a list of results (paginated data) as objects with attributes.
272
+ When return_response=True, returns tuple of (data, response).
273
+
274
+ If a side_effect was configured, raises that exception instead.
275
+
276
+ Args:
277
+ use_etag (bool): Whether to use ETag (ignored in stub)
278
+ return_response (bool): Whether to return response object
279
+ force_refresh (bool): Whether to force refresh (ignored in stub)
280
+ use_cache (bool): Whether to use cache (ignored in stub)
281
+ Returns:
282
+ list[Any]: Test data as list of objects, or tuple of (data, response) if return_response=True
283
+ Raises:
284
+ Exception: if side_effect was configured
285
+ """
286
+ # If side_effect is configured, raise it
287
+ if self._side_effect is not None:
288
+ # Support both exception instances and lists of exceptions/values
289
+ if isinstance(self._side_effect, list):
290
+ # Pop from list for sequential side effects
291
+ if self._side_effect:
292
+ effect = self._side_effect.pop(0)
293
+ if isinstance(effect, Exception):
294
+ raise effect
295
+ # If not an exception, return it as data
296
+ data = _to_pydantic_model_instance("SideEffect", effect)
297
+ result_data = data if isinstance(data, list) else [data]
298
+ return (
299
+ (result_data, MockResponse())
300
+ if return_response
301
+ else result_data
302
+ )
303
+ elif isinstance(self._side_effect, Exception):
304
+ raise self._side_effect
305
+
306
+ # Convert to Pydantic model instances first
307
+ data = _to_pydantic_model_instance("Results", self._test_data)
308
+
309
+ # If test data is already a list, use it as is
310
+ if isinstance(data, list):
311
+ result_data = data
312
+ else:
313
+ # If single item, wrap in list
314
+ result_data = [data]
315
+
316
+ if return_response:
317
+ # Return tuple of (data, response)
318
+ response = MockResponse()
319
+ return (result_data, response)
320
+
321
+ return result_data
322
+
323
+
324
+ class EsiCategoryStub:
325
+ """
326
+ Stub for an ESI category (e.g., Skills, Character, Wallet).
327
+
328
+ This class holds methods for a specific ESI category and returns
329
+ EsiOperationStub instances when methods are called.
330
+ """
331
+
332
+ def __init__(
333
+ self,
334
+ category_name: str,
335
+ test_data: dict[str, Any],
336
+ endpoints: dict[str, EsiEndpoint],
337
+ ):
338
+ """
339
+ Initialize the category stub.
340
+
341
+ Attributes:
342
+ category_name (str): Name of the ESI category
343
+ test_data (dict[str, Any]): Test data for methods in this category
344
+ endpoints (dict[str, EsiEndpoint]): Endpoint definitions for this category
345
+ """
346
+ self._category_name = category_name
347
+ self._test_data = test_data
348
+ self._endpoints = endpoints
349
+
350
+ def __getattr__(self, method_name: str) -> callable:
351
+ """
352
+ Return a callable that creates an EsiOperationStub when invoked.
353
+
354
+ Args:
355
+ method_name (str): Name of the ESI method
356
+ Returns:
357
+ callable: Callable that returns EsiOperationStub
358
+ Raises:
359
+ AttributeError: If method is not registered in endpoints
360
+ """
361
+
362
+ def operation_caller(**kwargs) -> EsiOperationStub:
363
+ """
364
+ Create and return an operation stub with test data and optional side effect.
365
+
366
+ :return: Operation stub with test data
367
+ :rtype: EsiOperationStub
368
+ :raises AttributeError: If endpoints were provided and this method is not registered
369
+ """
370
+ # Check if endpoint is registered
371
+ endpoint = self._endpoints.get(method_name)
372
+
373
+ # Only registered methods are allowed
374
+ if endpoint is None:
375
+ raise AttributeError(
376
+ f"Method '{self._category_name}.{method_name}' is not registered. "
377
+ f"Available methods: {list(self._endpoints.keys())}"
378
+ )
379
+
380
+ # Look up test data for this method
381
+ method_data = self._test_data.get(method_name, {})
382
+
383
+ # print(f"DEBUG: EsiCategoryStub: method_data for {self._category_name}.{method_name}: {method_data}")
384
+ data = _select_method_data(method_data, kwargs, endpoint)
385
+ # print(f"DEBUG: EsiCategoryStub: selected data for {self._category_name}.{method_name}: {data}")
386
+
387
+ # Get side effect from endpoint if defined
388
+ side_effect = endpoint.side_effect if endpoint else None
389
+
390
+ return EsiOperationStub(test_data=data, side_effect=side_effect)
391
+
392
+ return operation_caller
393
+
394
+
395
+ class EsiClientStub:
396
+ """
397
+ Stub for ESI OpenAPI client that mimics ESIClientProvider.client.
398
+
399
+ This class provides access to ESI categories and their methods,
400
+ returning test data instead of making real API calls.
401
+ """
402
+
403
+ def __init__(
404
+ self,
405
+ test_data_config: dict[str, dict[str, Any]],
406
+ endpoints: list[EsiEndpoint],
407
+ ):
408
+ """
409
+ Initialize the ESI client stub.
410
+
411
+ Args:
412
+ test_data_config (dict[str, dict[str, Any]]): Test data configuration
413
+ Format: {"CategoryName": {"MethodName": test_data}}
414
+ endpoints (list[EsiEndpoint]): List of endpoint definitions (REQUIRED)
415
+ Raises:
416
+ ValueError: If endpoints is None or empty
417
+ """
418
+ if not endpoints:
419
+ raise ValueError(
420
+ "endpoints parameter is required and cannot be empty. "
421
+ "You must provide a list of EsiEndpoint definitions."
422
+ )
423
+
424
+ self._test_data_config = test_data_config
425
+ self._categories = {}
426
+ self._endpoints_by_category = {}
427
+
428
+ # Build endpoint lookup by category and method
429
+ for endpoint in endpoints:
430
+ if endpoint.category not in self._endpoints_by_category:
431
+ self._endpoints_by_category[endpoint.category] = {}
432
+ self._endpoints_by_category[endpoint.category][endpoint.method] = endpoint
433
+
434
+ # Create category stubs only for categories that have registered endpoints
435
+ for category_name in self._endpoints_by_category.keys():
436
+ methods_data = test_data_config.get(category_name, {})
437
+ category_endpoints = self._endpoints_by_category[category_name]
438
+ self._categories[category_name] = EsiCategoryStub(
439
+ category_name=category_name,
440
+ test_data=methods_data,
441
+ endpoints=category_endpoints,
442
+ )
443
+
444
+ def __getattr__(self, category_name: str) -> EsiCategoryStub:
445
+ """
446
+ Return the category stub for the requested ESI category.
447
+
448
+ Args:
449
+ category_name (str): Name of the ESI category
450
+ Returns:
451
+ EsiCategoryStub: The category stub
452
+ Raises:
453
+ AttributeError: If category is not registered in endpoints
454
+ """
455
+ if category_name in self._categories:
456
+ return self._categories[category_name]
457
+
458
+ # Only registered categories are allowed
459
+ raise AttributeError(
460
+ f"Category '{category_name}' is not registered. "
461
+ f"Available categories: {list(self._categories.keys())}"
462
+ )
463
+
464
+
465
+ def load_test_data_from_json(file_name: str = "esi_test_data.json") -> dict:
466
+ """
467
+ Load test data from a JSON file in the testdata directory.
468
+
469
+ Args:
470
+ file_name (str): Name of the JSON file (default: "esi_test_data.json")
471
+
472
+ Returns:
473
+ dict: Loaded test data
474
+ """
475
+ file_path = Path(__file__).parent / file_name
476
+
477
+ if not file_path.exists():
478
+ return {}
479
+
480
+ with file_path.open("r", encoding="utf-8") as fp:
481
+ return json.load(fp)
482
+
483
+
484
+ def create_esi_client_stub(
485
+ test_data_config: dict[str, dict[str, Any]] | None = None,
486
+ endpoints: list[EsiEndpoint] | None = None,
487
+ ) -> EsiClientStub:
488
+ """
489
+ Create an ESI client stub with the provided test data configuration.
490
+ This function can be used in tests to provide a stub ESI client.
491
+
492
+ Args:
493
+ test_data_config (dict[str, dict[str, Any]] | None): Test data configuration, if None loads from JSON file
494
+ endpoints (list[EsiEndpoint] | None): List of endpoint definitions (REQUIRED)
495
+
496
+ Returns:
497
+ EsiClientStub: ESI client stub
498
+
499
+ Raises:
500
+ ValueError: If endpoints is None or empty
501
+ """
502
+ if test_data_config is None:
503
+ test_data_config = load_test_data_from_json()
504
+
505
+ if not endpoints:
506
+ raise ValueError(
507
+ "endpoints parameter is required. "
508
+ "You must provide a list of EsiEndpoint definitions."
509
+ )
510
+
511
+ return EsiClientStub(test_data_config=test_data_config, endpoints=endpoints)
File without changes
@@ -3,7 +3,6 @@ import json
3
3
  from pathlib import Path
4
4
 
5
5
  # Alliance Auth (External Libs)
6
- from eveuniverse.models import EveMoon, EveType
7
6
  from eveuniverse.tools.testdata import load_testdata_from_dict
8
7
 
9
8
 
@@ -0,0 +1,13 @@
1
+ """Generate AllianceAuth test objects from allianceauth.json."""
2
+
3
+ # Standard Library
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ def _load_planetary_data():
9
+ with open(Path(__file__).parent / "planetary.json", encoding="utf-8") as fp:
10
+ return json.load(fp)
11
+
12
+
13
+ _planetary_data = _load_planetary_data()