django-esi 8.0.0a4__py3-none-any.whl → 8.0.0b2__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 django-esi might be problematic. Click here for more details.

Files changed (48) hide show
  1. {django_esi-8.0.0a4.dist-info → django_esi-8.0.0b2.dist-info}/METADATA +5 -3
  2. {django_esi-8.0.0a4.dist-info → django_esi-8.0.0b2.dist-info}/RECORD +48 -47
  3. esi/__init__.py +1 -1
  4. esi/aiopenapi3/plugins.py +99 -3
  5. esi/clients.py +56 -7
  6. esi/decorators.py +26 -10
  7. esi/exceptions.py +7 -3
  8. esi/helpers.py +38 -0
  9. esi/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  10. esi/locale/cs_CZ/LC_MESSAGES/django.po +2 -2
  11. esi/locale/de/LC_MESSAGES/django.mo +0 -0
  12. esi/locale/de/LC_MESSAGES/django.po +2 -2
  13. esi/locale/en/LC_MESSAGES/django.mo +0 -0
  14. esi/locale/en/LC_MESSAGES/django.po +2 -2
  15. esi/locale/es/LC_MESSAGES/django.mo +0 -0
  16. esi/locale/es/LC_MESSAGES/django.po +2 -2
  17. esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  18. esi/locale/fr_FR/LC_MESSAGES/django.po +2 -2
  19. esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  20. esi/locale/it_IT/LC_MESSAGES/django.po +2 -2
  21. esi/locale/ja/LC_MESSAGES/django.mo +0 -0
  22. esi/locale/ja/LC_MESSAGES/django.po +2 -2
  23. esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  24. esi/locale/ko_KR/LC_MESSAGES/django.po +2 -2
  25. esi/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  26. esi/locale/nl_NL/LC_MESSAGES/django.po +2 -2
  27. esi/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  28. esi/locale/pl_PL/LC_MESSAGES/django.po +2 -2
  29. esi/locale/ru/LC_MESSAGES/django.mo +0 -0
  30. esi/locale/ru/LC_MESSAGES/django.po +2 -2
  31. esi/locale/sk/LC_MESSAGES/django.mo +0 -0
  32. esi/locale/sk/LC_MESSAGES/django.po +2 -2
  33. esi/locale/uk/LC_MESSAGES/django.mo +0 -0
  34. esi/locale/uk/LC_MESSAGES/django.po +2 -2
  35. esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  36. esi/locale/zh_Hans/LC_MESSAGES/django.po +2 -2
  37. esi/managers.pyi +3 -0
  38. esi/openapi_clients.py +188 -44
  39. esi/rate_limiting.py +50 -21
  40. esi/signals.py +21 -0
  41. esi/stubs.pyi +9 -9
  42. esi/tests/__init__.py +33 -11
  43. esi/tests/test_clients.py +77 -19
  44. esi/tests/test_decorators.py +61 -1
  45. esi/tests/test_openapi.json +65 -2
  46. esi/tests/test_openapi.py +512 -18
  47. {django_esi-8.0.0a4.dist-info → django_esi-8.0.0b2.dist-info}/WHEEL +0 -0
  48. {django_esi-8.0.0a4.dist-info → django_esi-8.0.0b2.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-esi
3
- Version: 8.0.0a4
3
+ Version: 8.0.0b2
4
4
  Summary: Django app for accessing the EVE Swagger Interface (ESI).
5
5
  Keywords: eveonline
6
6
  Author-email: Alliance Auth <adarnof@gmail.com>
@@ -9,7 +9,6 @@ Description-Content-Type: text/markdown
9
9
  Classifier: Environment :: Web Environment
10
10
  Classifier: Framework :: Django
11
11
  Classifier: Framework :: Django :: 4.2
12
- Classifier: Framework :: Django :: 5.1
13
12
  Classifier: Framework :: Django :: 5.2
14
13
  Classifier: Intended Audience :: Developers
15
14
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -20,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Internet :: WWW/HTTP
24
24
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
25
25
  License-File: LICENSE
@@ -27,8 +27,10 @@ Requires-Dist: aiopenapi3
27
27
  Requires-Dist: bravado>=10.6,<12
28
28
  Requires-Dist: celery>=4.0.2
29
29
  Requires-Dist: django>=4.2,<6
30
- Requires-Dist: httpx[http2, brotli, zstd]
30
+ Requires-Dist: django-redis>=5.2
31
+ Requires-Dist: httpx[brotli, http2, zstd]
31
32
  Requires-Dist: jsonschema<4
33
+ Requires-Dist: pydantic>=2.12.3
32
34
  Requires-Dist: python-jose>=3.3
33
35
  Requires-Dist: requests>=2.26
34
36
  Requires-Dist: requests-oauthlib>=0.8
@@ -1,52 +1,53 @@
1
- esi/__init__.py,sha256=AJRGqO80Mo5rJTmvrH2T1RcuhIWbecQzOXbjP_812Ow,205
1
+ esi/__init__.py,sha256=LAZyiO_5dNVnNVqzSKrR9GaxMevoW_XH4GLTr0VO4pQ,204
2
2
  esi/admin.py,sha256=9i68WwW_gR0zsGhJKTWK2yMEi44EkhpAaOW0LDpVfM8,1176
3
3
  esi/app_settings.py,sha256=2Jp_1myzKCL8A20RYvwQnQd04Ds_CBGsCqwDtsrYf0M,4289
4
4
  esi/apps.py,sha256=HIu1niTkOXYmCzMVAjYcaFhHrrXeBbHvui6I44OCHXw,280
5
5
  esi/checks.py,sha256=31puQdsrpRUJB-kedp2k7Evo0x2knTWQPZsCUUrJ3dY,1912
6
- esi/clients.py,sha256=WB5YseJnfZdTeJ9C1Eze-iWXEqIpo5i_8uiQ6w0VkCw,23503
7
- esi/decorators.py,sha256=2RmPdkrIAwbxOV5Ls8-RIWd_VhmXEDzIG6g5-bs6WZc,9093
6
+ esi/clients.py,sha256=kFEV9T2GcfWRBhEM7njeantRR8g96LsXo6HNWeQCyoQ,25281
7
+ esi/decorators.py,sha256=OGGpbYaVZESk2BA9o69gBoBAdWUEGP1pUKohAEoykiM,10055
8
8
  esi/errors.py,sha256=KfFtgX8Mys4uMoQtco0n_pQeaM83yVrRSyW6StXuUjo,308
9
- esi/exceptions.py,sha256=nR-Z0BXVt5xSkgUL56E97pqtTyEu3MFr8_P0uCCCQ-E,1448
10
- esi/helpers.py,sha256=-lYojtcWYWtlC_olSItWR3O3zJpjMYTQpNvsax81us8,672
9
+ esi/exceptions.py,sha256=-6aAr1JNEeOcVcVteDvSPopQkv5kGEj5sjnUlcJWTZQ,1506
10
+ esi/helpers.py,sha256=GZoDNTjZ0adW1ukhuGY1o58s_sZbvjhbVaJS95wZKpg,2207
11
11
  esi/managers.py,sha256=zdri1aSJrSX9W_kwzbXq44CAAhdufikoOViaJntA41A,11443
12
- esi/managers.pyi,sha256=BxRNX2yOT3domdrxsuGp4G0TrP_bWmf8VVwvM03eHQE,1977
12
+ esi/managers.pyi,sha256=3_GxkRNXK3OzvaTrgW31Syhsns1UBKjr669iFKuhxQE,2065
13
13
  esi/models.py,sha256=MEY8jmHGwA8ZJjq37h-vC8LnOVn_Mnv5Gw0NoI9tkXk,11484
14
- esi/openapi_clients.py,sha256=8NoWqH24z3nOCo4JOhcMJJkY9TGmBXdpDb4zsCD60WQ,40135
15
- esi/rate_limiting.py,sha256=fUzso1YCHlBes8SVrEAsP58eRWcFNuO2XEkW6RQLhUE,2600
14
+ esi/openapi_clients.py,sha256=hMeFzsovT_tC4x8f9a6u4LaUnL2QIKsKZILGlsCAWEg,45525
15
+ esi/rate_limiting.py,sha256=0lbFyRWbw2dPjJLXMEix8PDNh7nuAw8INaGf2cQaFDU,3353
16
+ esi/signals.py,sha256=JQbkrL9l0EbKoB1EgFl8CwFBlZ2GmWBM5HUp3Rc1uYw,512
16
17
  esi/stubs.py,sha256=UakysEAq5V454oKbxyEUewncr5sCFZCgwHFh0SJvU3Y,30
17
- esi/stubs.pyi,sha256=gJzK6pizZV0OGIojAE0qfe5gtTod3YaOemRgGnI8R08,244125
18
+ esi/stubs.pyi,sha256=ua9zHzZtrWct1mj_UV1pN7HJyA2RxqWPYapL7DQMRYA,244089
18
19
  esi/tasks.py,sha256=TaE_Q03oLk1ohkPw8KknXpIK-nY47C-oaGUUu4nr8pw,2658
19
20
  esi/urls.py,sha256=B4EnmT-MZzs4F8a9ORK1ejAHbpGRN_V4TKks8ww3JGE,151
20
21
  esi/views.py,sha256=cq2JkqSAwx-Nqi3amRUA9CKLu3ZLQYHSAH8c8g7xIQ4,3917
21
- esi/aiopenapi3/plugins.py,sha256=4erd3dL6bSMEp_AeB7JWnRZPnOSRrZHK5QsHaGJilBg,5001
22
- esi/locale/cs_CZ/LC_MESSAGES/django.mo,sha256=WyxsjDEeArGMNhtpY2eCJjdENMDmmxdxz4oW1s4N5eA,391
23
- esi/locale/cs_CZ/LC_MESSAGES/django.po,sha256=f4fVr2btQpIeeMaqZL0tpj0j06Evwe2xPHh1OH6gPuU,1318
24
- esi/locale/de/LC_MESSAGES/django.mo,sha256=uNJPd25k3AUU6FceBsJV9pqCpM1ZW4wDDFKNZkPlXqg,874
25
- esi/locale/de/LC_MESSAGES/django.po,sha256=YVZKMGC0jeZltrhlY2I4wXWmlf5tRgjw6IcLpIUkNsk,1564
26
- esi/locale/en/LC_MESSAGES/django.mo,sha256=gtTXjZjZ18WCvehANkW-Uo_YU4dOolu57DYXtWnOZkA,434
27
- esi/locale/en/LC_MESSAGES/django.po,sha256=LGrMRv02ftr0c470FRija9WOrz4NZcLor7XR_O2WGgQ,1365
28
- esi/locale/es/LC_MESSAGES/django.mo,sha256=XegUUE14RGv9SYxO7FoJjz2jNvG64hA8oOFhqjYZc8g,791
29
- esi/locale/es/LC_MESSAGES/django.po,sha256=OvqlzHQkuaODF1IZhTWj22d3zvtzLCYgX-XQZdbcyZg,1568
30
- esi/locale/fr_FR/LC_MESSAGES/django.mo,sha256=FFAiYNUgD1zv056mRf3actNPDDIagu3Bb13EXaAlV94,697
31
- esi/locale/fr_FR/LC_MESSAGES/django.po,sha256=vAiDmUPmqP9AGs6rqo-mX3rsda6QZFQvADMr6pmGlgk,1577
32
- esi/locale/it_IT/LC_MESSAGES/django.mo,sha256=SicuHgSlkdIV-Jc9QuR4JaC8QwfycDznJR0yuP9jCoI,931
33
- esi/locale/it_IT/LC_MESSAGES/django.po,sha256=sh7kXEfDlJy94VCwknwQbJMobn9xCNA1_zuzs_keLHc,1624
34
- esi/locale/ja/LC_MESSAGES/django.mo,sha256=0900q-CQ5WT98zWn746YfdOy51uE_nlEbJyxogqxrvc,761
35
- esi/locale/ja/LC_MESSAGES/django.po,sha256=4NBEUf6NwYI_DempuOkjaZFuJ_71aWsEZS6yHhGsRpw,1537
36
- esi/locale/ko_KR/LC_MESSAGES/django.mo,sha256=7NktQKVols2D9r8LH0fVmNXtF4L3tmKyAf90IoRhSAY,800
37
- esi/locale/ko_KR/LC_MESSAGES/django.po,sha256=J-qdtO_4P9j0-qyfRDbw9M_BIEiSKSU_tq3IbcNsFFQ,1572
38
- esi/locale/nl_NL/LC_MESSAGES/django.mo,sha256=WyxsjDEeArGMNhtpY2eCJjdENMDmmxdxz4oW1s4N5eA,391
39
- esi/locale/nl_NL/LC_MESSAGES/django.po,sha256=f4fVr2btQpIeeMaqZL0tpj0j06Evwe2xPHh1OH6gPuU,1318
40
- esi/locale/pl_PL/LC_MESSAGES/django.mo,sha256=WyxsjDEeArGMNhtpY2eCJjdENMDmmxdxz4oW1s4N5eA,391
41
- esi/locale/pl_PL/LC_MESSAGES/django.po,sha256=f4fVr2btQpIeeMaqZL0tpj0j06Evwe2xPHh1OH6gPuU,1318
42
- esi/locale/ru/LC_MESSAGES/django.mo,sha256=g9515s_BB2pyCJe9-oqPXGEU7vc0EdFoh1SpTUCkGus,1128
43
- esi/locale/ru/LC_MESSAGES/django.po,sha256=s5p5D5ILhwQJr2eoe11xPdDxR-BttWGh1OuQ3PZV4Lk,1879
44
- esi/locale/sk/LC_MESSAGES/django.mo,sha256=eWUijXSFZNruadb-y-FXso0-1a9lbp6zUPWS2kMoZi4,509
45
- esi/locale/sk/LC_MESSAGES/django.po,sha256=FuCsSPv580yZ7py6bWL_PgBA4cj7HmmS4-4-hz4l518,1443
46
- esi/locale/uk/LC_MESSAGES/django.mo,sha256=LlEo98zaX1whlyZTn_UqXyNOa5rKleqem1XcoKvZ40E,656
47
- esi/locale/uk/LC_MESSAGES/django.po,sha256=46ZA2mjbgClqMK-I0FnBIKiTYOVoDMs-o5M8QgcHsfk,1596
48
- esi/locale/zh_Hans/LC_MESSAGES/django.mo,sha256=O2csJQMFxmvSWM4iNfNozHAQjAAd76LwFy0sx3oLHCY,735
49
- esi/locale/zh_Hans/LC_MESSAGES/django.po,sha256=tNaPILQzZK2v6YyBxmq3FOKWdQUFn-9I2kWpF3thBKk,1512
22
+ esi/aiopenapi3/plugins.py,sha256=YpDn5cRLi6t9dtpnmSHo-Wl6Or4R0LRQY22ZKWLFolk,8269
23
+ esi/locale/cs_CZ/LC_MESSAGES/django.mo,sha256=3a89M28nD3rnPeG3u0p9XZrasew2oDpvg0dRhlwW5l4,391
24
+ esi/locale/cs_CZ/LC_MESSAGES/django.po,sha256=4-C8ELza_J3-ExnfMx1-XD1l6LA01IYxLeKra1FID4A,1317
25
+ esi/locale/de/LC_MESSAGES/django.mo,sha256=a9XLVfAxszxPY0DQegtSG6VJzV5QhdXZH1LdZa8t7Kw,874
26
+ esi/locale/de/LC_MESSAGES/django.po,sha256=QWBbIAxSEPEGZ2y2G3Ox8sw7ye3WrZBG-bigZFS__ZA,1563
27
+ esi/locale/en/LC_MESSAGES/django.mo,sha256=AcLiFv8Ch9eSXQY4XXFJILtnWuMW5xsTc41U7bf02Zw,434
28
+ esi/locale/en/LC_MESSAGES/django.po,sha256=UoFGhStLCb53j8jFDZFj2RPnRzZK31x2If-unnXrUcI,1364
29
+ esi/locale/es/LC_MESSAGES/django.mo,sha256=PUG8ZeipGOVYeOHzwum1CmgPLfOO0T2Fbw0w0XoRMxE,791
30
+ esi/locale/es/LC_MESSAGES/django.po,sha256=2iMKh-JyFqKJzdFWP90qwaG6YZ9PNP-JpeaOEI4gAlM,1567
31
+ esi/locale/fr_FR/LC_MESSAGES/django.mo,sha256=IVLtTvbYL1RSC2TUmQaS8XHqqq-nAieJ7y1iU2K5GEg,697
32
+ esi/locale/fr_FR/LC_MESSAGES/django.po,sha256=zohifdzM0Id0eD-tDsAUfSv6ivhlIR09IqjlUhQPZU0,1576
33
+ esi/locale/it_IT/LC_MESSAGES/django.mo,sha256=74zINJDrN03tah_3hs5OxtrqOwGGCQqJHj0Q_fdZkRg,931
34
+ esi/locale/it_IT/LC_MESSAGES/django.po,sha256=k-SUOUQUWoIVzZjQDPJZPDnyHznBVDsv__OAy9-qFgc,1623
35
+ esi/locale/ja/LC_MESSAGES/django.mo,sha256=3UhDTEtBm6XdY_NBtNGwpoKQvzgPJVc-E4bBST4LuXg,761
36
+ esi/locale/ja/LC_MESSAGES/django.po,sha256=zWf0y38JbzlVVaENvK16emzSlFGdTwM2b9ap4NOWsE8,1536
37
+ esi/locale/ko_KR/LC_MESSAGES/django.mo,sha256=wUhdQt0lbIhzilvTIF2u2CLqx25IUQIs_I2dok5ldT0,800
38
+ esi/locale/ko_KR/LC_MESSAGES/django.po,sha256=YPTvyMEmJPdvCwPuJYZb0hU4-LEoF9Cd_gLt8J4SA1s,1571
39
+ esi/locale/nl_NL/LC_MESSAGES/django.mo,sha256=3a89M28nD3rnPeG3u0p9XZrasew2oDpvg0dRhlwW5l4,391
40
+ esi/locale/nl_NL/LC_MESSAGES/django.po,sha256=4-C8ELza_J3-ExnfMx1-XD1l6LA01IYxLeKra1FID4A,1317
41
+ esi/locale/pl_PL/LC_MESSAGES/django.mo,sha256=3a89M28nD3rnPeG3u0p9XZrasew2oDpvg0dRhlwW5l4,391
42
+ esi/locale/pl_PL/LC_MESSAGES/django.po,sha256=4-C8ELza_J3-ExnfMx1-XD1l6LA01IYxLeKra1FID4A,1317
43
+ esi/locale/ru/LC_MESSAGES/django.mo,sha256=fpU4q7cD7UUd7EXqxSkRinujmqaHw56mJilBeA-6JlU,1128
44
+ esi/locale/ru/LC_MESSAGES/django.po,sha256=1UrIOg2Ff4IaKnApdXULs__XcgRHYC0-I7R4qxNdC_A,1878
45
+ esi/locale/sk/LC_MESSAGES/django.mo,sha256=U1ACkWEmbwABZ7SoLiRyE7a2exXUoezT-F3mwIJ2se0,509
46
+ esi/locale/sk/LC_MESSAGES/django.po,sha256=g3DYCmxk_L7Q8V_BHgcAdQp4Ct5e6hPlrUiTDakNwO0,1442
47
+ esi/locale/uk/LC_MESSAGES/django.mo,sha256=M94bI7wiqPEHODk0MiuXvcvwkHsbJjml-JNtXKwBw5Q,656
48
+ esi/locale/uk/LC_MESSAGES/django.po,sha256=1ylXyZuZZx6ZNdLVfvm7mROkjs9VqUYpgK5m_4qR948,1595
49
+ esi/locale/zh_Hans/LC_MESSAGES/django.mo,sha256=P-oIXYliRFn9qWM1cBbBbkg0bnLN_pKTfHLiwlPN2xw,735
50
+ esi/locale/zh_Hans/LC_MESSAGES/django.po,sha256=AN10U8DZ0LsIN8_TmVHDXbgZM34nGmZ3ouM-55OVyi8,1511
50
51
  esi/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
52
  esi/management/commands/generate_esi_stubs.py,sha256=dunWHFSRb6NFu5emeDdskqIt3YqnEqQLBiuNERMkdmE,9798
52
53
  esi/management/commands/migrate_to_ssov2.py,sha256=x9W4xaefrNoG_dNscHIWrWTRk4SuVGIW0s44q2QBN9k,6427
@@ -71,27 +72,27 @@ esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png,sha256=al3NRjIngZtrNzEx
71
72
  esi/templates/esi/select_token.html,sha256=qofOjKWHco_sSuoEJoLRsXP_doK_barHvkF7h5LLxjY,6941
72
73
  esi/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
74
  esi/templatetags/scope_tags.py,sha256=5-7vEwe-B91xo7oSnn-tSuudHSJCoGIhbddDpNrqUnw,167
74
- esi/tests/__init__.py,sha256=1ymL-74_4alt8AQNs7cFiXO5MmQKlIeOcA4OPOdAitM,3092
75
+ esi/tests/__init__.py,sha256=yecRpeG9tsVnyeKm6jEo2QcO2QeYJF_vOT5ACzfNSec,3704
75
76
  esi/tests/client_authed_pilot.py,sha256=GN0J59TcscHM9HZH6OaPODM9Zel3mvObjJ4Dmmzc2j8,1622
76
77
  esi/tests/client_public_pilot.py,sha256=u6P-qbdYSvQ6A_MJqt8Jrkswt-u-FlvYDKtCvA5-PMo,1252
77
78
  esi/tests/factories.py,sha256=2pjkSYKDfO50bzX1nNcUHz_FAAJs7k07u7R8om6AlP0,1375
78
79
  esi/tests/factories_2.py,sha256=25NqVe9zzHkl4U1EN3R7pjdN2ahIBXxrrJhQzZhvw58,1972
79
80
  esi/tests/jwt_factory.py,sha256=0Jj9TjEv18h2XVsRpYc7BWHr3CPtQJgfKeB-9mdHevk,4919
80
81
  esi/tests/test_checks.py,sha256=ntq2ijuJ-6pxGruNh21rM4GlXDLVm-o3sy8DrFbjwEM,1846
81
- esi/tests/test_clients.py,sha256=1JWTBV83nZbt82e37DAnJ7h4gm9m_xU1ZDvEjt0sHsc,41363
82
- esi/tests/test_decorators.py,sha256=I5MhcLKhN5xD4PxgEQqOWQ2kiXquDqQIEve9dbTOsho,15069
82
+ esi/tests/test_clients.py,sha256=qoiv3OztuhjizNknZplJhwnMzAlX689MGad4Azu809M,44163
83
+ esi/tests/test_decorators.py,sha256=XvnubUR7ue3tG2chc2rSMw737tCN6r7SYxf-V8KJED8,16595
83
84
  esi/tests/test_management_command.py,sha256=mtxfBtG6CHP1bTy1tJEO-djX4SQwMT3T_mqGnUIM6qs,9863
84
85
  esi/tests/test_managers.py,sha256=CEpjXSXyVY6xxgrs0f9FnEh-KY9JPaKjKYiutXLLShE,24309
85
86
  esi/tests/test_models.py,sha256=lDj5IcYgXHeOFTHXTsWGg13CSooFaxD0c51kJn_ytAc,13748
86
- esi/tests/test_openapi.json,sha256=rGvX02M_LmMoWRTVZZ7NkGzc3-lQy7-2WQCrt6DlqM0,11828
87
- esi/tests/test_openapi.py,sha256=yWgm7clO_RfWxOWGmAYFr0oAp1jzgFmnbIRWjDucp9E,7988
87
+ esi/tests/test_openapi.json,sha256=3jGT9Vo75wEOqJY1Iy4_uJSJfEs8iwSv107fIsspWM8,13901
88
+ esi/tests/test_openapi.py,sha256=XERYRw-cFbb2XCuW28AEybsTcT-_up9wEumcL57Nexw,22837
88
89
  esi/tests/test_swagger.json,sha256=HOrPgbvwm5N521QNcE3baWcZJkSjmuN_VWrR06wEQoo,17241
89
90
  esi/tests/test_swagger_full.json,sha256=JCEAZNMFhkdZhquTx4lDhrqGgCrzzzlYU64HdbEv8E4,2548369
90
91
  esi/tests/test_tasks.py,sha256=nIvfXax_8nQCxNQeT-4TJ84i0__t1qWe155z5rnsktQ,4399
91
92
  esi/tests/test_templatetags.py,sha256=b68JWE3HvOlr2aUisJHsTsDS4e7IMjDeqTuzMqC7Re4,517
92
93
  esi/tests/test_views.py,sha256=Kj_f2yIpmPG0kx-lAX_sfkaHlIpgbkm02ieA1V3o-k4,13073
93
94
  esi/tests/threading_pilot.py,sha256=ax_dEdnTNibA-UQHqbZle_2dh_3jcHKRyrYSOKuE_6U,1931
94
- django_esi-8.0.0a4.dist-info/licenses/LICENSE,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
95
- django_esi-8.0.0a4.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
96
- django_esi-8.0.0a4.dist-info/METADATA,sha256=71zaRb8BRkKRsA6L8vFFb34wbnDqUmQw639Kh1hCE9s,4738
97
- django_esi-8.0.0a4.dist-info/RECORD,,
95
+ django_esi-8.0.0b2.dist-info/licenses/LICENSE,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
96
+ django_esi-8.0.0b2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
97
+ django_esi-8.0.0b2.dist-info/METADATA,sha256=JYa14wSYhfIBFbXE5gInFnIMEcJf2b2_21NQUQc0dq4,4815
98
+ django_esi-8.0.0b2.dist-info/RECORD,,
esi/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Django app for accessing the EVE Swagger Interface (ESI)."""
2
2
 
3
- __version__ = "8.0.0-alpha.4"
3
+ __version__ = "8.0.0-beta.2"
4
4
  __title__ = 'Django-ESI'
5
5
  __url__ = 'https://gitlab.com/allianceauth/django-esi'
6
6
  __build_date__ = "2025-09-18"
esi/aiopenapi3/plugins.py CHANGED
@@ -1,10 +1,20 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ from collections.abc import Generator
5
+ from django.conf import settings
6
+
1
7
  from aiopenapi3.plugin import Document, Init
2
8
 
9
+ logger = logging.getLogger(__name__)
10
+
11
+
3
12
  class Trim204ContentType(Document):
4
13
  """
5
14
  Removes and content-type from responses on a 204 reponses
6
15
  A 204 never has content...
7
16
  """
17
+
8
18
  def parsed(self, ctx: Document.Context) -> Document.Context:
9
19
  spec = ctx.document
10
20
  # Patch all paths
@@ -18,12 +28,95 @@ class Trim204ContentType(Document):
18
28
  return ctx
19
29
 
20
30
 
31
+ def find_refs_recursively(data: Any, parent: Any = None) -> Generator[Any, None, None]:
32
+ """
33
+ Recursively searches for all instances of "#ref" in a dict+children and returns schemas.
34
+ """
35
+ if isinstance(data, dict):
36
+ for key, value in data.items():
37
+ if key == "$ref":
38
+ if "#/components/schemas/" in value:
39
+ next = value.split("/")[-1]
40
+ yield next
41
+ if parent:
42
+ yield from find_refs_recursively(parent[next], parent)
43
+ yield from find_refs_recursively(value, parent)
44
+ elif isinstance(data, list):
45
+ for item in data:
46
+ yield from find_refs_recursively(item, parent)
47
+
48
+
49
+ class MinifySpec(Document):
50
+ """
51
+ Removes operations and schemas from spec to limit memory spam
52
+ """
53
+
54
+ def __init__(self, tags: list[str], operations: list[str]):
55
+ super().__init__()
56
+ self.keep_tags = set(tags)
57
+ self.keep_ops = operations
58
+
59
+ def parsed(self, ctx: Document.Context) -> Document.Context:
60
+
61
+ if len(self.keep_tags) == 0 and self.keep_ops == []:
62
+ logger.error("No tag/path filtering supplied to ESI Client. Using all tags. This throw an error with `DEBUG=False`!", stack_info=True)
63
+ if not getattr(settings, "DEBUG", False):
64
+ # we are in production mode throw error to protect RAM.
65
+ raise AttributeError("No tag/path filtering supplied to ESI Client.")
66
+ # We are in debug mode allow an unfiltered client.
67
+ return ctx
68
+
69
+ # filter the client.
70
+ spec = ctx.document
71
+
72
+ remove_paths = set()
73
+ keep_schema = set()
74
+ logger.debug("Filtering Paths/Tags: ")
75
+ for name, path_item in spec.get("paths", {}).items():
76
+ keep = False
77
+ for method_name in ("get", "post", "put", "delete", "patch", "options", "head"):
78
+ method = path_item.get(method_name)
79
+ if not method:
80
+ continue
81
+ if len(self.keep_tags.intersection(method['tags'])) or method["operationId"] in self.keep_ops:
82
+ keep = True
83
+ schemas = find_refs_recursively(
84
+ path_item,
85
+ spec["components"]["schemas"] # find all sub schema's
86
+ )
87
+ for s in schemas:
88
+ keep_schema.add(s)
89
+
90
+ if not keep:
91
+ remove_paths.add(name)
92
+ else:
93
+ logger.debug(f" - {name}")
94
+
95
+ # remove the paths we don't care for
96
+ for name in remove_paths:
97
+ spec["paths"].pop(name)
98
+
99
+ # build new schema from what we need
100
+ logger.debug("Rebuilding Schema: ")
101
+ new_schema = {}
102
+ for name, data in spec["components"]["schemas"].items():
103
+ if name in keep_schema:
104
+ logger.debug(f" - {name}")
105
+ new_schema[name] = data
106
+
107
+ # replace schemas with the new schema
108
+ ctx.document["components"]["schemas"] = new_schema
109
+
110
+ return ctx
111
+
112
+
21
113
  class Add304ContentType(Document):
22
114
  """
23
115
  Adds 304 content-type to responses
24
116
  A 304 never has content. ESI defualt has application/json
25
117
  This is a hack for now
26
118
  """
119
+
27
120
  def parsed(self, ctx: Document.Context) -> Document.Context:
28
121
  spec = ctx.document
29
122
  # Patch all paths
@@ -33,7 +126,7 @@ class Add304ContentType(Document):
33
126
  if not method:
34
127
  continue
35
128
  if "304" not in method['responses']:
36
- method['responses']["304"]={
129
+ method['responses']["304"] = {
37
130
  "description": "Not Modified"
38
131
  }
39
132
  return ctx
@@ -43,6 +136,7 @@ class RemoveSecurityParameter(Document):
43
136
  """
44
137
  Removes the whole OAuth2 securityScheme
45
138
  """
139
+
46
140
  def parsed(self, ctx: Document.Context) -> Document.Context:
47
141
  print("RemoveSecurityParameterPlugin: Removing OAuth2 securityScheme")
48
142
  spec = ctx.document
@@ -63,6 +157,7 @@ class TrimSecurityParameter(Document):
63
157
  Trims out of Spec OAuth2 attributes. CCP have fixed this.
64
158
  Leaving in place in case we need a quick reference again.
65
159
  """
160
+
66
161
  def parsed(self, ctx: Document.Context) -> Document.Context:
67
162
  print("TrimSecurityParameter: Trimming out of spec attributes")
68
163
  spec = ctx.document
@@ -80,8 +175,9 @@ class PatchCompatibilityDatePlugin(Document):
80
175
  This is because WE specifically add it in the library to the HTTP requests,
81
176
  but without this, it will be a required parameter during request generation before it hits the HTTP library.
82
177
  """
178
+
83
179
  def parsed(self, ctx: Document.Context) -> Document.Context:
84
- print("PatchCompatibilityDatePlugin: making compatibility date optional")
180
+ logger.debug("PatchCompatibilityDatePlugin: making compatibility date optional")
85
181
  spec = ctx.document
86
182
 
87
183
  def patch_param(param):
@@ -124,5 +220,5 @@ class DjangoESIInit(Init):
124
220
 
125
221
  def initialized(self, ctx: Init.Context) -> Init.Context:
126
222
  # Force the app_name into the api client class for etags
127
- self.api.app_name=self.app_name
223
+ self.api.app_name = self.app_name
128
224
  return ctx # noqa
esi/clients.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import json
2
2
  import logging
3
+ from timeit import default_timer
3
4
  import warnings
4
- from datetime import datetime
5
+ import datetime as dt
5
6
  from hashlib import md5
6
7
  from time import sleep
7
8
  from typing import Any
@@ -10,7 +11,7 @@ from urllib import parse as urlparse
10
11
  from bravado import requests_client
11
12
  from bravado.client import SwaggerClient
12
13
  from bravado.exception import (
13
- HTTPBadGateway, HTTPGatewayTimeout, HTTPServiceUnavailable,
14
+ HTTPBadGateway, HTTPGatewayTimeout, HTTPServiceUnavailable, HTTPError,
14
15
  )
15
16
  from bravado.http_future import HttpFuture
16
17
  from bravado.swagger_model import Loader
@@ -22,6 +23,7 @@ from django.core.cache import cache
22
23
 
23
24
  from . import __title__, __url__, __version__, app_settings
24
25
  from .errors import TokenExpiredError
26
+ from .signals import esi_request_statistics
25
27
 
26
28
  logger = logging.getLogger(__name__)
27
29
 
@@ -68,8 +70,10 @@ class CachingHttpFuture(HttpFuture):
68
70
  seconds until "Expires" time
69
71
  """
70
72
  try:
71
- expires_dt = datetime.strptime(str(expires), '%a, %d %b %Y %H:%M:%S %Z')
72
- delta = expires_dt - datetime.utcnow()
73
+ expires_dt = dt.datetime.strptime(str(expires), '%a, %d %b %Y %H:%M:%S %Z')
74
+ if expires_dt.tzinfo is None:
75
+ expires_dt = expires_dt.replace(tzinfo=dt.timezone.utc)
76
+ delta = expires_dt - dt.datetime.now(dt.timezone.utc)
73
77
  return delta.total_seconds()
74
78
  except ValueError:
75
79
  return 0
@@ -142,6 +146,19 @@ class CachingHttpFuture(HttpFuture):
142
146
  for language in my_languages
143
147
  }
144
148
 
149
+ def _send_signal(self, status_code: int, headers: dict = {}, latency: float = 0) -> None:
150
+ """
151
+ Dispatch the esi request statistics signal
152
+ """
153
+ esi_request_statistics.send(
154
+ sender=self.__class__,
155
+ operation=self.operation.path_name,
156
+ status_code=status_code,
157
+ headers=headers,
158
+ latency=latency,
159
+ bucket=""
160
+ )
161
+
145
162
  def result(self, **kwargs) -> Any | tuple[Any, IncomingResponse]:
146
163
  """Executes the request and returns the response from ESI. Response will
147
164
  include the requested / first page only if there are more pages available.
@@ -189,6 +206,9 @@ class CachingHttpFuture(HttpFuture):
189
206
  )
190
207
 
191
208
  if cached:
209
+ self._send_signal(
210
+ status_code=0
211
+ )
192
212
  result, response = cached
193
213
  expiry = self._time_to_expiry(str(response.headers.get('Expires')))
194
214
  if expiry < 0:
@@ -243,6 +263,7 @@ class CachingHttpFuture(HttpFuture):
243
263
 
244
264
  retries = 0
245
265
  while retries <= max_retries:
266
+ _t = default_timer()
246
267
  try:
247
268
  if app_settings.ESI_INFO_LOGGING_ENABLED:
248
269
  params = self.future.request.params
@@ -267,6 +288,11 @@ class CachingHttpFuture(HttpFuture):
267
288
  logger.debug('ESI response content: %s', response.text)
268
289
  break
269
290
  except (HTTPBadGateway, HTTPGatewayTimeout, HTTPServiceUnavailable) as ex:
291
+ self._send_signal(
292
+ status_code=ex.status_code,
293
+ headers=ex.response.headers,
294
+ latency=default_timer() - _t
295
+ )
270
296
  if retries < max_retries:
271
297
  retries += 1
272
298
  logger.warning(
@@ -283,7 +309,23 @@ class CachingHttpFuture(HttpFuture):
283
309
  sleep(wait_secs)
284
310
  else:
285
311
  raise ex
312
+ except HTTPError as ex:
313
+ """
314
+ Throw any other error into the signal
315
+ then just re-raise
316
+ """
317
+ self._send_signal(
318
+ status_code=ex.status_code,
319
+ headers=ex.response.headers,
320
+ latency=default_timer() - _t
321
+ )
322
+ raise ex
286
323
 
324
+ self._send_signal(
325
+ status_code=response.status_code,
326
+ headers=response.headers,
327
+ latency=default_timer() - _t
328
+ )
287
329
  # restore original value
288
330
  self.request_config.also_return_response = _also_return_response
289
331
  return result, response
@@ -482,19 +524,26 @@ def esi_client_factory(
482
524
 
483
525
  client = RequestsClientPlus()
484
526
 
527
+ from esi.helpers import pascal_case_string
528
+ sanitized_appname = pascal_case_string(__title__)
529
+
485
530
  if app_info_text:
486
531
  # app_info_text (email@example) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi)
487
532
  # Deprecated
488
- user_agent = f"{app_info_text} ({app_settings.ESI_USER_CONTACT_EMAIL}) {__title__}/{__version__} (+{__url__})"
533
+ user_agent = f"{app_info_text} ({app_settings.ESI_USER_CONTACT_EMAIL}) {sanitized_appname}/{__version__} (+{__url__})"
489
534
  elif ua_appname is None or ua_version is None:
490
535
  # Django-ESI/1.2.3 () (email@example; +https://gitlab.com/allianceauth/django-esi)
491
536
  # Deprecated
492
- user_agent = f"{__title__}/{__version__} ({app_settings.ESI_USER_CONTACT_EMAIL}; +{__url__})"
537
+ user_agent = f"{sanitized_appname}/{__version__} ({app_settings.ESI_USER_CONTACT_EMAIL}; +{__url__})"
493
538
  else:
494
539
  # AppName/1.2.3 (email@example.com) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi)
495
540
  # or AppName/1.2.3 (email@example.com; +https://gitlab.com/) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi) (+https://gitlab.com/allianceauth/django-esi)
496
541
  # Preferred
497
- user_agent = f"{ua_appname}/{ua_version} ({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{ua_url})' if ua_url else ')'} {__title__}/{__version__} (+{__url__})"
542
+
543
+ # Enforce PascalCase for `ua_appname` and strip whitespace
544
+ sanitized_ua_appname = pascal_case_string(ua_appname)
545
+
546
+ user_agent = f"{sanitized_ua_appname}/{ua_version} ({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{ua_url})' if ua_url else ')'} {sanitized_appname}/{__version__} (+{__url__})"
498
547
 
499
548
  client.user_agent = user_agent
500
549
 
esi/decorators.py CHANGED
@@ -1,7 +1,8 @@
1
- import functools
2
1
  import logging
3
2
  import time
4
3
  from functools import wraps
4
+ from typing import Any
5
+ from collections.abc import Callable
5
6
 
6
7
  from django.core.cache import cache
7
8
 
@@ -227,12 +228,20 @@ def single_use_token(scopes='', new=False):
227
228
  return decorator
228
229
 
229
230
 
230
- def wait_for_esi_errorlimit_reset(cache_key="esi_error_limit_reset", poll_interval=1):
231
+ def wait_for_esi_errorlimit_reset(cache_key="esi_error_limit_reset", poll_interval=1) -> Callable[..., Callable[..., Any]]:
232
+ """
233
+ Decorator to apply a polling sleep while the ESI Server/Client is in an Error Limit state
234
+ The preferred non-blocking method is to retry your tasks after the limit reset time has passed
235
+
236
+ Args:
237
+ cache_key (str, optional): NOT USUALLY CHANGED. Defaults to "esi_error_limit_reset".
238
+ poll_interval (int, optional): Interval in seconds to poll redis. Defaults to 1.
239
+ """
231
240
  def decorator(func):
232
241
  def wrapper(*args, **kwargs):
233
242
  reset = cache.get(cache_key)
234
243
  if reset is not None:
235
- print(f"ESI Error Limited, waiting {reset}s before retrying...")
244
+ logger.error(f"ESI Error Limited, waiting {reset}s before retrying...")
236
245
  while cache.get(cache_key):
237
246
  time.sleep(poll_interval)
238
247
  return func(*args, **kwargs)
@@ -240,16 +249,23 @@ def wait_for_esi_errorlimit_reset(cache_key="esi_error_limit_reset", poll_interv
240
249
  return decorator
241
250
 
242
251
 
243
- def esi_rate_limiter_bucketed(
244
- bucket: ESIRateLimitBucket,
245
- raise_on_limit: bool = True,
246
- ):
247
- # TODO Investigate esi cache hits.
252
+ def esi_rate_limiter_bucketed(bucket: ESIRateLimitBucket, raise_on_limit: bool = True):
253
+ """
254
+ Decorator for custom manual rate limits on some endpoints to apply a polling sleep while the bucket is exhausted.
255
+ MARKET_DATA_HISTORY
256
+ CHARACTER_CORPORATION_HISTORY
257
+ The preferred non-blocking method is to retry your tasks after the limit reset time has passed
258
+
248
259
 
260
+ Args:
261
+ bucket (ESIRateLimitBucket): The Bucket to rate limit against
262
+ raise_on_limit (bool, optional): Whether to raise an Exception when the limit is reached. Defaults to True.
263
+ """
264
+ # TODO Investigate esi cache hits.
249
265
  def decorator(func):
250
- @functools.wraps(func)
266
+ @wraps(func)
251
267
  def wrapper(*args, **kwargs):
252
- ESIRateLimits.check_bucket(bucket, raise_on_limit)
268
+ ESIRateLimits.check_decr_bucket(bucket, raise_on_limit)
253
269
  return func(*args, **kwargs)
254
270
  return wrapper
255
271
  return decorator
esi/exceptions.py CHANGED
@@ -4,10 +4,12 @@ from aiopenapi3.errors import HTTPServerError as base_HTTPServerError
4
4
  from aiopenapi3.errors import HTTPClientError as base_HTTPClientError
5
5
  from aiopenapi3.errors import HTTPError
6
6
 
7
+
7
8
  class ESIErrorLimitException(Exception):
8
9
  """ESI Global Error Limit Exceeded
9
10
  https://developers.eveonline.com/docs/services/esi/best-practices/#error-limit
10
11
  """
12
+
11
13
  def __init__(self, reset=None, *args, **kwargs) -> None:
12
14
  self.reset = reset
13
15
  msg = kwargs.get("message") or (
@@ -18,8 +20,10 @@ class ESIErrorLimitException(Exception):
18
20
 
19
21
  class ESIBucketLimitException(Exception):
20
22
  """Endpoint (Bucket) Specific Rate Limit Exceeded"""
21
- def __init__(self, bucket, *args, **kwargs) -> None:
23
+
24
+ def __init__(self, bucket, reset: float = 0, *args, **kwargs) -> None:
22
25
  self.bucket = bucket
26
+ self.reset = reset
23
27
  msg = kwargs.get("message") or f"ESI bucket limit reached for {bucket}."
24
28
  super().__init__(msg, *args)
25
29
 
@@ -37,11 +41,11 @@ class HTTPNotModified(HTTPError):
37
41
 
38
42
  @dataclasses.dataclass(repr=False)
39
43
  class HTTPClientError(base_HTTPClientError):
40
- """response code 4xx"""
44
+ """HTTP Response Code 4xx"""
41
45
  pass
42
46
 
43
47
 
44
48
  @dataclasses.dataclass(repr=False)
45
49
  class HTTPServerError(base_HTTPServerError):
46
- """response code 5xx"""
50
+ """HTTP Response Code 5xx"""
47
51
  pass
esi/helpers.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from esi.models import Token
2
+ from string import capwords
2
3
 
3
4
 
4
5
  def get_token(character_id: int, scopes: list) -> Token:
@@ -23,3 +24,40 @@ def get_token(character_id: int, scopes: list) -> Token:
23
24
  f"No valid token found for character_id={character_id} with required scopes."
24
25
  )
25
26
  return token
27
+
28
+
29
+ def pascal_case_string(string: str) -> str:
30
+ """
31
+ Convert a string to PascalCase by capitalizing the first letter of each word and removing spaces,
32
+ but only if the string contains spaces or hyphens.
33
+
34
+ This function checks if the input string contains spaces or hyphens. If so, it replaces hyphens with spaces,
35
+ capitalizes the first letter of each word, removes the spaces, and returns the resulting PascalCase string.
36
+ If the input string does not contain spaces or hyphens, it is returned unchanged.
37
+
38
+ Behaviour:
39
+ Any string containing spaces or hyphens will be converted to PascalCase.
40
+ Strings without spaces or hyphens will be returned unchanged.
41
+ This gives you the opportunity to use already formatted strings as needed.
42
+
43
+ Examples:
44
+ - "app name" -> "AppName"
45
+ - "app-name" -> "AppName"
46
+ - "appname" -> "appname"
47
+ - "AppName" -> "AppName"
48
+ - "appName" -> "appName"
49
+ - "app_name" -> "app_name"
50
+
51
+ :param string: The input string to be converted to PascalCase.
52
+ :type string: str
53
+ :return: The PascalCase formatted string, or the original string if no spaces or hyphens are present.
54
+ :rtype: str
55
+ """
56
+
57
+ # Check if the string contains spaces or hyphens
58
+ if any(c in string for c in (" ", "-")):
59
+ # Replace hyphens with spaces, capitalize each word, and remove spaces
60
+ return capwords(string.replace("-", " ")).replace(" ", "")
61
+
62
+ # Return the original string if no spaces or hyphens are present
63
+ return string
Binary file
@@ -6,9 +6,9 @@
6
6
  #, fuzzy
7
7
  msgid ""
8
8
  msgstr ""
9
- "Project-Id-Version: Django ESI 8.0.0-alpha.4\n"
9
+ "Project-Id-Version: Django ESI 8.0.0-beta.2\n"
10
10
  "Report-Msgid-Bugs-To: https://gitlab.com/allianceauth/django-esi/-/issues\n"
11
- "POT-Creation-Date: 2025-09-21 13:23+1000\n"
11
+ "POT-Creation-Date: 2025-10-31 15:18+1000\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
Binary file
@@ -9,9 +9,9 @@
9
9
  #, fuzzy
10
10
  msgid ""
11
11
  msgstr ""
12
- "Project-Id-Version: Django ESI 8.0.0-alpha.4\n"
12
+ "Project-Id-Version: Django ESI 8.0.0-beta.2\n"
13
13
  "Report-Msgid-Bugs-To: https://gitlab.com/allianceauth/django-esi/-/issues\n"
14
- "POT-Creation-Date: 2025-09-21 13:23+1000\n"
14
+ "POT-Creation-Date: 2025-10-31 15:18+1000\n"
15
15
  "PO-Revision-Date: 2023-10-25 11:04+0000\n"
16
16
  "Last-Translator: Peter Pfeufer, 2023\n"
17
17
  "Language-Team: German (https://app.transifex.com/alliance-auth/teams/107430/"