django-esi 8.0.0a1__tar.gz → 8.0.0a3__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.
Potentially problematic release.
This version of django-esi might be problematic. Click here for more details.
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/PKG-INFO +3 -4
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/__init__.py +2 -2
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/aiopenapi3/plugins.py +39 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/app_settings.py +1 -1
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/exceptions.py +29 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/en/LC_MESSAGES/django.po +3 -3
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/ru/LC_MESSAGES/django.po +8 -7
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/management/commands/generate_esi_stubs.py +33 -16
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/openapi_clients.py +154 -36
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/stubs.pyi +483 -567
- django_esi-8.0.0a3/esi/tests/test_openapi.py +14 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/pyproject.toml +4 -4
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/LICENSE +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/README.md +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/admin.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/apps.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/checks.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/clients.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/decorators.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/errors.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/helpers.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/de/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/de/LC_MESSAGES/django.po +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/en/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/es/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/es/LC_MESSAGES/django.po +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/fr_FR/LC_MESSAGES/django.po +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/it_IT/LC_MESSAGES/django.po +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/ja/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/ja/LC_MESSAGES/django.po +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/ko_KR/LC_MESSAGES/django.po +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/ru/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/management/commands/__init__.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/management/commands/migrate_to_ssov2.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/managers.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/managers.pyi +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0001_initial.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0002_scopes_20161208.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0003_hide_tokens_from_admin_site.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0004_remove_unique_access_token.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0005_remove_token_length_limit.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0006_remove_url_length_limit.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0007_fix_mysql_8_migration.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0008_nullable_refresh_token.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0009_set_old_tokens_to_sso_v1.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0010_set_new_tokens_to_sso_v2.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0011_add_token_indices.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0012_fix_token_type_choices.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/0013_squashed_0012_fix_token_type_choices.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/migrations/__init__.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/models.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/rate_limiting.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/static/esi/img/EVE_SSO_Login_Buttons_Large_Black.png +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/static/esi/img/EVE_SSO_Login_Buttons_Large_White.png +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/static/esi/img/EVE_SSO_Login_Buttons_Small_Black.png +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/stubs.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tasks.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/templates/esi/select_token.html +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/templatetags/__init__.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/templatetags/scope_tags.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/__init__.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/client_authed_pilot.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/client_public_pilot.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/factories.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/factories_2.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/jwt_factory.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_checks.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_clients.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_decorators.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_management_command.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_managers.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_models.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_swagger.json +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_swagger_full.json +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_tasks.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_templatetags.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/test_views.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/tests/threading_pilot.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/urls.py +0 -0
- {django_esi-8.0.0a1 → django_esi-8.0.0a3}/esi/views.py +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-esi
|
|
3
|
-
Version: 8.0.
|
|
3
|
+
Version: 8.0.0a3
|
|
4
4
|
Summary: Django app for accessing the EVE Swagger Interface (ESI).
|
|
5
|
+
Keywords: eveonline
|
|
5
6
|
Author-email: Alliance Auth <adarnof@gmail.com>
|
|
6
|
-
Requires-Python: >=3.
|
|
7
|
+
Requires-Python: >=3.10
|
|
7
8
|
Description-Content-Type: text/markdown
|
|
8
9
|
Classifier: Environment :: Web Environment
|
|
9
10
|
Classifier: Framework :: Django
|
|
@@ -15,8 +16,6 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
|
15
16
|
Classifier: Operating System :: OS Independent
|
|
16
17
|
Classifier: Programming Language :: Python
|
|
17
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
from aiopenapi3.plugin import Document
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
class Trim204ContentType(Document):
|
|
5
|
+
"""
|
|
6
|
+
Removes and content-type from responses on a 204 reponses
|
|
7
|
+
A 204 never has content...
|
|
8
|
+
"""
|
|
9
|
+
def parsed(self, ctx: Document.Context) -> Document.Context:
|
|
10
|
+
spec = ctx.document
|
|
11
|
+
# Patch all paths
|
|
12
|
+
for path_item in spec.get("paths", {}).values():
|
|
13
|
+
for method_name in ("get", "post", "put", "delete", "patch", "options", "head"):
|
|
14
|
+
method = path_item.get(method_name)
|
|
15
|
+
if not method:
|
|
16
|
+
continue
|
|
17
|
+
if "204" in method['responses']:
|
|
18
|
+
method['responses']["204"].pop("content", [])
|
|
19
|
+
return ctx
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Add304ContentType(Document):
|
|
23
|
+
"""
|
|
24
|
+
Adds 304 content-type to responses
|
|
25
|
+
A 304 never has content. ESI defualt has application/json
|
|
26
|
+
This is a hack for now
|
|
27
|
+
"""
|
|
28
|
+
def parsed(self, ctx: Document.Context) -> Document.Context:
|
|
29
|
+
spec = ctx.document
|
|
30
|
+
# Patch all paths
|
|
31
|
+
for path_item in spec.get("paths", {}).values():
|
|
32
|
+
for method_name in ("get", "post", "put", "delete", "patch", "options", "head"):
|
|
33
|
+
method = path_item.get(method_name)
|
|
34
|
+
if not method:
|
|
35
|
+
continue
|
|
36
|
+
if "304" not in method['responses']:
|
|
37
|
+
method['responses']["304"]={
|
|
38
|
+
"description": "Not Modified"
|
|
39
|
+
}
|
|
40
|
+
return ctx
|
|
41
|
+
|
|
42
|
+
|
|
4
43
|
class RemoveSecurityParameter(Document):
|
|
5
44
|
"""
|
|
6
45
|
Removes the whole OAuth2 securityScheme
|
|
@@ -108,5 +108,5 @@ ESI_TOKEN_JWT_AUDIENCE = str(getattr(settings, "ESI_TOKEN_JWT_AUDIENCE", "EVE On
|
|
|
108
108
|
|
|
109
109
|
# list of all official language codes supported by ESI
|
|
110
110
|
ESI_LANGUAGES = getattr(settings, 'ESI_LANGUAGES', [
|
|
111
|
-
'
|
|
111
|
+
'en', 'de', 'fr', 'ja', 'ru', 'zh', 'ko', 'es'
|
|
112
112
|
])
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
|
|
3
|
+
from aiopenapi3.errors import HTTPServerError as base_HTTPServerError
|
|
4
|
+
from aiopenapi3.errors import HTTPClientError as base_HTTPClientError
|
|
5
|
+
from aiopenapi3.errors import HTTPError
|
|
6
|
+
|
|
1
7
|
class ESIErrorLimitException(Exception):
|
|
2
8
|
"""ESI Global Error Limit Exceeded
|
|
3
9
|
https://developers.eveonline.com/docs/services/esi/best-practices/#error-limit
|
|
@@ -16,3 +22,26 @@ class ESIBucketLimitException(Exception):
|
|
|
16
22
|
self.bucket = bucket
|
|
17
23
|
msg = kwargs.get("message") or f"ESI bucket limit reached for {bucket}."
|
|
18
24
|
super().__init__(msg, *args)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclasses.dataclass(repr=False)
|
|
28
|
+
class HTTPNotModified(HTTPError):
|
|
29
|
+
"""The HTTP Status is 304"""
|
|
30
|
+
|
|
31
|
+
status_code: int
|
|
32
|
+
headers: dict[str, str]
|
|
33
|
+
|
|
34
|
+
def __str__(self):
|
|
35
|
+
return f"""<{self.__class__.__name__} {self.status_code} {self.headers}>"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclasses.dataclass(repr=False)
|
|
39
|
+
class HTTPClientError(base_HTTPClientError):
|
|
40
|
+
"""response code 4xx"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclasses.dataclass(repr=False)
|
|
45
|
+
class HTTPServerError(base_HTTPServerError):
|
|
46
|
+
"""response code 5xx"""
|
|
47
|
+
pass
|
|
@@ -8,7 +8,7 @@ msgid ""
|
|
|
8
8
|
msgstr ""
|
|
9
9
|
"Project-Id-Version: PACKAGE VERSION\n"
|
|
10
10
|
"Report-Msgid-Bugs-To: \n"
|
|
11
|
-
"POT-Creation-Date: 2025-
|
|
11
|
+
"POT-Creation-Date: 2025-09-04 13:41+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"
|
|
@@ -18,11 +18,11 @@ msgstr ""
|
|
|
18
18
|
"Content-Transfer-Encoding: 8bit\n"
|
|
19
19
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
20
20
|
|
|
21
|
-
#: esi/models.py:
|
|
21
|
+
#: esi/models.py:66
|
|
22
22
|
msgid "Character"
|
|
23
23
|
msgstr ""
|
|
24
24
|
|
|
25
|
-
#: esi/models.py:
|
|
25
|
+
#: esi/models.py:67
|
|
26
26
|
msgid "Corporation"
|
|
27
27
|
msgstr ""
|
|
28
28
|
|
|
@@ -5,15 +5,16 @@
|
|
|
5
5
|
#
|
|
6
6
|
# Translators:
|
|
7
7
|
# Filipp Chertiev <f@fzfx.ru>, 2023
|
|
8
|
+
# Gnevich <and.vareba81@gmail.com>, 2025
|
|
8
9
|
#
|
|
9
10
|
#, fuzzy
|
|
10
11
|
msgid ""
|
|
11
12
|
msgstr ""
|
|
12
13
|
"Project-Id-Version: PACKAGE VERSION\n"
|
|
13
14
|
"Report-Msgid-Bugs-To: \n"
|
|
14
|
-
"POT-Creation-Date:
|
|
15
|
+
"POT-Creation-Date: 2025-06-30 10:57+1000\n"
|
|
15
16
|
"PO-Revision-Date: 2023-10-25 11:04+0000\n"
|
|
16
|
-
"Last-Translator:
|
|
17
|
+
"Last-Translator: Gnevich <and.vareba81@gmail.com>, 2025\n"
|
|
17
18
|
"Language-Team: Russian (https://app.transifex.com/alliance-auth/teams/107430/ru/)\n"
|
|
18
19
|
"MIME-Version: 1.0\n"
|
|
19
20
|
"Content-Type: text/plain; charset=UTF-8\n"
|
|
@@ -21,13 +22,13 @@ msgstr ""
|
|
|
21
22
|
"Language: ru\n"
|
|
22
23
|
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
|
23
24
|
|
|
24
|
-
#: esi/models.py:
|
|
25
|
+
#: esi/models.py:73
|
|
25
26
|
msgid "Character"
|
|
26
|
-
msgstr ""
|
|
27
|
+
msgstr "Персонаж"
|
|
27
28
|
|
|
28
|
-
#: esi/models.py:
|
|
29
|
+
#: esi/models.py:74
|
|
29
30
|
msgid "Corporation"
|
|
30
|
-
msgstr ""
|
|
31
|
+
msgstr "Корпорация"
|
|
31
32
|
|
|
32
33
|
#: esi/templates/esi/select_token.html:13
|
|
33
34
|
msgid "ESI Token Selection"
|
|
@@ -49,7 +50,7 @@ msgstr "Новый персонаж"
|
|
|
49
50
|
#: esi/templates/esi/select_token.html:71
|
|
50
51
|
#: esi/templates/esi/select_token.html:107
|
|
51
52
|
msgid "Add Token"
|
|
52
|
-
msgstr ""
|
|
53
|
+
msgstr "Добавить токен"
|
|
53
54
|
|
|
54
55
|
#: esi/templates/esi/select_token.html:88
|
|
55
56
|
#: esi/templates/esi/select_token.html:89
|
|
@@ -102,7 +102,7 @@ class Command(BaseCommand):
|
|
|
102
102
|
f.write("# flake8: noqa=E501\n")
|
|
103
103
|
f.write("# Auto Generated do not edit\n")
|
|
104
104
|
# Python Imports
|
|
105
|
-
f.write("from typing import Any
|
|
105
|
+
f.write("from typing import Any\n\n")
|
|
106
106
|
f.write("from esi.openapi_clients import EsiOperation\n")
|
|
107
107
|
f.write("from esi.models import Token\n\n\n")
|
|
108
108
|
|
|
@@ -114,15 +114,25 @@ class Command(BaseCommand):
|
|
|
114
114
|
# result(), Results(), Results_Localized() etc. all live here
|
|
115
115
|
ops = stub_api._operationindex._tags[tag]
|
|
116
116
|
for nm, op in sorted(ops._operations.items()):
|
|
117
|
+
op_type = op[0]
|
|
117
118
|
op_obj = op[2]
|
|
118
119
|
docstring = (op_obj.description or op_obj.summary or "").replace("\n", " ").strip()
|
|
119
120
|
op_class_name = sanitize_operation_class(nm)
|
|
120
121
|
|
|
121
122
|
response_type = "Any"
|
|
122
123
|
try:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
match op_type:
|
|
125
|
+
case "post":
|
|
126
|
+
resp_201 = op_obj.responses.get("201")
|
|
127
|
+
if resp_201 and "application/json" in resp_201.content:
|
|
128
|
+
response_type = schema_to_type(resp_201.content["application/json"].schema_)
|
|
129
|
+
case "put" | "delete":
|
|
130
|
+
response_type = "None"
|
|
131
|
+
case _:
|
|
132
|
+
resp_200 = op_obj.responses.get("200")
|
|
133
|
+
if resp_200 and "application/json" in resp_200.content:
|
|
134
|
+
response_type = schema_to_type(resp_200.content["application/json"].schema_)
|
|
135
|
+
|
|
126
136
|
except Exception:
|
|
127
137
|
response_type = "Any"
|
|
128
138
|
|
|
@@ -130,22 +140,26 @@ class Command(BaseCommand):
|
|
|
130
140
|
|
|
131
141
|
if op_class_name not in operation_classes:
|
|
132
142
|
f.write(f"class {op_class_name}(EsiOperation):\n")
|
|
133
|
-
|
|
143
|
+
if response_type != "None":
|
|
144
|
+
f.write(" \"\"\"EsiOperation, use result(), results() or results_localized()\"\"\"\n")
|
|
145
|
+
else:
|
|
146
|
+
f.write(" \"\"\"EsiOperation, use result()\"\"\"\n")
|
|
134
147
|
|
|
135
148
|
# result()
|
|
136
149
|
f.write(f" def result(self, etag: str | None = None, return_response: bool = False, use_cache: bool = True, **extra) -> {response_type}:\n") # noqa: E501
|
|
137
150
|
f.write(f" \"\"\"{docstring}\"\"\"\n") if docstring else None
|
|
138
151
|
f.write(" ...\n\n")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
if response_type != "None":
|
|
153
|
+
# We only need the extra utility functions if its actually an endpoint that returns data
|
|
154
|
+
# results()
|
|
155
|
+
f.write(f" def results(self, etag: str | None = None, return_response: bool = False, use_cache: bool = True, **extra) -> {results_type}:\n") # noqa: E501
|
|
156
|
+
f.write(f" \"\"\"{docstring}\"\"\"\n") if docstring else None
|
|
157
|
+
f.write(" ...\n\n")
|
|
158
|
+
|
|
159
|
+
# results_localized()
|
|
160
|
+
f.write(f" def results_localized(self, languages: list[str] | str | None = None, **extra) -> dict[str, {results_type}]:\n")
|
|
161
|
+
f.write(f" \"\"\"{docstring}\"\"\"\n") if docstring else None
|
|
162
|
+
f.write(" ...\n\n\n")
|
|
149
163
|
|
|
150
164
|
operation_classes[op_class_name] = True
|
|
151
165
|
|
|
@@ -172,6 +186,9 @@ class Command(BaseCommand):
|
|
|
172
186
|
|
|
173
187
|
params = ["self"]
|
|
174
188
|
optional_params = []
|
|
189
|
+
if getattr(op_obj, "requestBody", None):
|
|
190
|
+
params.append(f"body: {schema_to_type(op_obj.requestBody.content['application/json'].schema_)}")
|
|
191
|
+
|
|
175
192
|
for p in getattr(op_obj, "parameters", []):
|
|
176
193
|
required = getattr(p, "required", False)
|
|
177
194
|
schema_type_value = getattr(getattr(p, "schema_", None), "type", None)
|
|
@@ -181,7 +198,7 @@ class Command(BaseCommand):
|
|
|
181
198
|
param_type = "Any"
|
|
182
199
|
default = ""
|
|
183
200
|
if not required:
|
|
184
|
-
param_type = f"
|
|
201
|
+
param_type = f"{param_type} | None"
|
|
185
202
|
default = " = ..."
|
|
186
203
|
param_name = p.name.replace("-", "_")
|
|
187
204
|
if param_name == "authorization" and needs_oauth:
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import warnings
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, date
|
|
4
4
|
from hashlib import md5
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from aiopenapi3 import OpenAPI
|
|
8
8
|
from aiopenapi3._types import ResponseDataType, ResponseHeadersType
|
|
9
|
+
from aiopenapi3.errors import HTTPServerError as base_HTTPServerError
|
|
10
|
+
from aiopenapi3.errors import HTTPClientError as base_HTTPClientError
|
|
9
11
|
from aiopenapi3.request import OperationIndex, RequestBase
|
|
10
12
|
from httpx import (
|
|
11
13
|
AsyncClient, Client, HTTPStatusError, RequestError, Response, Timeout,
|
|
@@ -18,7 +20,8 @@ from tenacity import (
|
|
|
18
20
|
from django.core.cache import cache
|
|
19
21
|
|
|
20
22
|
from esi import app_settings
|
|
21
|
-
from esi.
|
|
23
|
+
from esi.exceptions import HTTPClientError, HTTPServerError, HTTPNotModified
|
|
24
|
+
from esi.aiopenapi3.plugins import Add304ContentType, PatchCompatibilityDatePlugin, Trim204ContentType
|
|
22
25
|
from esi.exceptions import ESIErrorLimitException
|
|
23
26
|
from esi.models import Token
|
|
24
27
|
from esi.stubs import ESIClientStub
|
|
@@ -121,14 +124,14 @@ def _load_aiopenapi_client_sync(
|
|
|
121
124
|
path=spec_file,
|
|
122
125
|
session_factory=session_factory,
|
|
123
126
|
use_operation_tags=True,
|
|
124
|
-
plugins=[PatchCompatibilityDatePlugin()]
|
|
127
|
+
plugins=[PatchCompatibilityDatePlugin(), Trim204ContentType(), Add304ContentType()]
|
|
125
128
|
)
|
|
126
129
|
else:
|
|
127
130
|
return OpenAPI.load_sync(
|
|
128
131
|
url=spec_url,
|
|
129
132
|
session_factory=session_factory,
|
|
130
133
|
use_operation_tags=True,
|
|
131
|
-
plugins=[PatchCompatibilityDatePlugin()]
|
|
134
|
+
plugins=[PatchCompatibilityDatePlugin(), Trim204ContentType(), Add304ContentType()]
|
|
132
135
|
)
|
|
133
136
|
|
|
134
137
|
|
|
@@ -176,14 +179,14 @@ async def _load_aiopenapi_client_async(
|
|
|
176
179
|
path=spec_file,
|
|
177
180
|
session_factory=session_factory,
|
|
178
181
|
use_operation_tags=True,
|
|
179
|
-
plugins=[PatchCompatibilityDatePlugin()]
|
|
182
|
+
plugins=[PatchCompatibilityDatePlugin(), Trim204ContentType(), Add304ContentType()]
|
|
180
183
|
)
|
|
181
184
|
else:
|
|
182
185
|
return await OpenAPI.load_async(
|
|
183
186
|
url=spec_url,
|
|
184
187
|
session_factory=session_factory,
|
|
185
188
|
use_operation_tags=True,
|
|
186
|
-
plugins=[PatchCompatibilityDatePlugin()]
|
|
189
|
+
plugins=[PatchCompatibilityDatePlugin(), Trim204ContentType(), Add304ContentType()]
|
|
187
190
|
)
|
|
188
191
|
|
|
189
192
|
|
|
@@ -322,10 +325,25 @@ class BaseEsiOperation():
|
|
|
322
325
|
Returns:
|
|
323
326
|
str: Key name
|
|
324
327
|
"""
|
|
325
|
-
|
|
328
|
+
# ignore the token this will break the cache
|
|
329
|
+
ignore_keys = [
|
|
330
|
+
"token",
|
|
331
|
+
]
|
|
332
|
+
_kwargs = { key:value for key, value in self._kwargs.items() if key not in ignore_keys }
|
|
333
|
+
data = (self.method + self.url + str(self._args) + str(_kwargs)).encode('utf-8')
|
|
326
334
|
str_hash = md5(data).hexdigest() # nosec B303
|
|
327
335
|
return f'esi_{str_hash}'
|
|
328
336
|
|
|
337
|
+
def _extract_body_param(self) -> Token | None:
|
|
338
|
+
"""Pop the request body from parameters to be able to check the param validity
|
|
339
|
+
Returns:
|
|
340
|
+
Any | None: the request body
|
|
341
|
+
"""
|
|
342
|
+
_body = self._kwargs.pop("body", None)
|
|
343
|
+
if _body and not getattr(self.operation, "requestBody", False):
|
|
344
|
+
raise ValueError("Request Body provided on endpoint with no request body paramater.")
|
|
345
|
+
return _body
|
|
346
|
+
|
|
329
347
|
def _extract_token_param(self) -> Token | None:
|
|
330
348
|
"""Pop token from parameters or use the Client wide token if set
|
|
331
349
|
Returns:
|
|
@@ -350,7 +368,7 @@ class BaseEsiOperation():
|
|
|
350
368
|
"""
|
|
351
369
|
return any(p.name == "before" or p.name == "after" for p in self.operation.parameters)
|
|
352
370
|
|
|
353
|
-
def _get_cache(self, cache_key: str) -> tuple[ResponseHeadersType | None, Any, Response | None]:
|
|
371
|
+
def _get_cache(self, cache_key: str, etag: str) -> tuple[ResponseHeadersType | None, Any, Response | None]:
|
|
354
372
|
"""Retrieve cached response and validate expiry
|
|
355
373
|
Args:
|
|
356
374
|
cache_key (str): The cache key to retrieve
|
|
@@ -366,12 +384,23 @@ class BaseEsiOperation():
|
|
|
366
384
|
|
|
367
385
|
if cached_response:
|
|
368
386
|
logger.debug(f"Cache Hit {self.url}")
|
|
369
|
-
|
|
387
|
+
expiry = _time_to_expiry(str(cached_response.headers.get('Expires')))
|
|
370
388
|
|
|
371
|
-
|
|
389
|
+
# force check to ensure cache isn't expired
|
|
372
390
|
if expiry < 0:
|
|
373
391
|
logger.warning("Cache expired by %d seconds, forcing expiry", expiry)
|
|
374
392
|
return None, None, None
|
|
393
|
+
|
|
394
|
+
# check if etag is same before building models from cache
|
|
395
|
+
if etag:
|
|
396
|
+
if cached_response.headers.get('Expires') == etag:
|
|
397
|
+
raise HTTPNotModified(
|
|
398
|
+
status_code=304,
|
|
399
|
+
headers=cached_response.headers
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# build models
|
|
403
|
+
headers, data = self.parse_cached_request(cached_response)
|
|
375
404
|
return headers, data, cached_response
|
|
376
405
|
|
|
377
406
|
return None, None, None
|
|
@@ -450,7 +479,7 @@ class EsiOperation(BaseEsiOperation):
|
|
|
450
479
|
req.req.headers["Authorization"] = f"Bearer {self.token.valid_access_token()}"
|
|
451
480
|
if etag:
|
|
452
481
|
req.req.headers["If-None-Match"] = etag
|
|
453
|
-
return req.request(parameters=self._unnormalize_parameters(parameters))
|
|
482
|
+
return req.request(data=self.body, parameters=self._unnormalize_parameters(parameters))
|
|
454
483
|
return retry(__func)
|
|
455
484
|
|
|
456
485
|
def result(
|
|
@@ -467,6 +496,7 @@ class EsiOperation(BaseEsiOperation):
|
|
|
467
496
|
"""
|
|
468
497
|
|
|
469
498
|
self.token = self._extract_token_param()
|
|
499
|
+
self.body = self._extract_body_param()
|
|
470
500
|
parameters = self._kwargs | extra
|
|
471
501
|
cache_key = self._cache_key()
|
|
472
502
|
etag_key = f"{cache_key}_etag"
|
|
@@ -474,7 +504,7 @@ class EsiOperation(BaseEsiOperation):
|
|
|
474
504
|
if not etag and app_settings.ESI_CACHE_RESPONSE:
|
|
475
505
|
etag = cache.get(etag_key)
|
|
476
506
|
|
|
477
|
-
headers, data, response = self._get_cache(cache_key)
|
|
507
|
+
headers, data, response = self._get_cache(cache_key, etag=etag) if use_cache else (None, None, None)
|
|
478
508
|
|
|
479
509
|
if response and use_cache:
|
|
480
510
|
expiry = _time_to_expiry(str(headers.get('Expires')))
|
|
@@ -488,17 +518,41 @@ class EsiOperation(BaseEsiOperation):
|
|
|
488
518
|
|
|
489
519
|
if not response:
|
|
490
520
|
logger.debug(f"Cache Miss {self.url}")
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
521
|
+
try:
|
|
522
|
+
headers, data, response = self._make_request(parameters, etag)
|
|
523
|
+
if response.status_code == 420:
|
|
524
|
+
reset = response.headers.get("X-RateLimit-Reset", None)
|
|
525
|
+
cache.set("esi_error_limit_reset", reset, timeout=reset)
|
|
526
|
+
raise ESIErrorLimitException(reset=reset)
|
|
527
|
+
self._store_cache(cache_key, response)
|
|
528
|
+
|
|
529
|
+
# if response.status_code == 304 and app_settings.ESI_CACHE_RESPONSE:
|
|
530
|
+
# cached = cache.get(cache_key)
|
|
531
|
+
# if cached:
|
|
532
|
+
# return (cached, response) if return_response else cached
|
|
533
|
+
# we dont want to do this, if we do this we have to store data longer than ttl. rip ram
|
|
534
|
+
|
|
535
|
+
# Shim our exceptions into Django-ESI
|
|
536
|
+
except base_HTTPServerError as e:
|
|
537
|
+
raise HTTPServerError(
|
|
538
|
+
status_code=e.status_code,
|
|
539
|
+
headers=e.headers,
|
|
540
|
+
data=e.data
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
except base_HTTPClientError as e:
|
|
544
|
+
raise HTTPClientError(
|
|
545
|
+
status_code=e.status_code,
|
|
546
|
+
headers=e.headers,
|
|
547
|
+
data=e.data
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
# Throw a 304 exception for catching.
|
|
551
|
+
if response.status_code == 304:
|
|
552
|
+
raise HTTPNotModified(
|
|
553
|
+
status_code=304,
|
|
554
|
+
headers=response.headers
|
|
555
|
+
)
|
|
502
556
|
|
|
503
557
|
return (data, response) if return_response else data
|
|
504
558
|
|
|
@@ -508,8 +562,8 @@ class EsiOperation(BaseEsiOperation):
|
|
|
508
562
|
return_response: bool = False,
|
|
509
563
|
use_cache: bool = True,
|
|
510
564
|
**extra) -> tuple[list[Any], Response | Any | None] | list[Any]:
|
|
511
|
-
all_results = []
|
|
512
|
-
last_response = None
|
|
565
|
+
all_results: list[Any] = []
|
|
566
|
+
last_response: Response | None = None
|
|
513
567
|
"""Executes the request and returns the response from ESI for the current
|
|
514
568
|
operation. Response will include all pages if there are more available.
|
|
515
569
|
|
|
@@ -557,10 +611,31 @@ class EsiOperation(BaseEsiOperation):
|
|
|
557
611
|
|
|
558
612
|
return (all_results, last_response) if return_response else all_results
|
|
559
613
|
|
|
560
|
-
def results_localized(
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
614
|
+
def results_localized(
|
|
615
|
+
self,
|
|
616
|
+
languages: list[str] | str | None = None,
|
|
617
|
+
**kwargs) -> dict[str, list[Any]]:
|
|
618
|
+
"""Executes the request and returns the response from ESI for all default languages and pages (if any).
|
|
619
|
+
Args:
|
|
620
|
+
languages: (list[str], str, optional) language(s) to return instead of default languages
|
|
621
|
+
Raises:
|
|
622
|
+
ValueError: Invalid or Not Supported Language Code ...
|
|
623
|
+
Returns:
|
|
624
|
+
dict[str, list[Any]]: Dict of all responses with the language code as keys.
|
|
625
|
+
"""
|
|
626
|
+
if not languages:
|
|
627
|
+
my_languages = list(app_settings.ESI_LANGUAGES)
|
|
628
|
+
else:
|
|
629
|
+
my_languages = []
|
|
630
|
+
for lang in dict.fromkeys(languages):
|
|
631
|
+
if lang not in app_settings.ESI_LANGUAGES:
|
|
632
|
+
raise ValueError('Invalid or Not Supported Language Code: %s' % lang)
|
|
633
|
+
my_languages.append(lang)
|
|
634
|
+
|
|
635
|
+
return {
|
|
636
|
+
language: self.results(accept_language=language, **kwargs)
|
|
637
|
+
for language in my_languages
|
|
638
|
+
}
|
|
564
639
|
|
|
565
640
|
def required_scopes(self) -> list[str]:
|
|
566
641
|
"""Return a simple list of scopes required for an endpoint. #Requires loading and processing a client
|
|
@@ -678,8 +753,31 @@ class EsiOperationAsync(BaseEsiOperation):
|
|
|
678
753
|
|
|
679
754
|
return (all_results, last_response) if return_response else all_results
|
|
680
755
|
|
|
681
|
-
|
|
682
|
-
|
|
756
|
+
def results_localized(
|
|
757
|
+
self,
|
|
758
|
+
languages: list[str] | str | None = None,
|
|
759
|
+
**extra) -> dict[str, list[Any]]:
|
|
760
|
+
"""Executes the request and returns the response from ESI for all default languages and pages (if any).
|
|
761
|
+
Args:
|
|
762
|
+
languages: (list[str], str, optional) language(s) to return instead of default languages
|
|
763
|
+
Raises:
|
|
764
|
+
ValueError: Invalid or Not Supported Language Code ...
|
|
765
|
+
Returns:
|
|
766
|
+
dict[str, list[Any]]: Dict of all responses with the language code as keys.
|
|
767
|
+
"""
|
|
768
|
+
if not languages:
|
|
769
|
+
my_languages = list(app_settings.ESI_LANGUAGES)
|
|
770
|
+
else:
|
|
771
|
+
my_languages = []
|
|
772
|
+
for lang in dict.fromkeys(languages):
|
|
773
|
+
if lang not in app_settings.ESI_LANGUAGES:
|
|
774
|
+
raise ValueError('Invalid or Not Supported Language Code: %s' % lang)
|
|
775
|
+
my_languages.append(lang)
|
|
776
|
+
|
|
777
|
+
return {
|
|
778
|
+
language: self.results(accept_language=language, **kwargs)
|
|
779
|
+
for language in my_languages
|
|
780
|
+
}
|
|
683
781
|
|
|
684
782
|
def required_scopes(self) -> list[str]:
|
|
685
783
|
"""Return a simple list of scopes required for an endpoint. #Requires loading and processing a client
|
|
@@ -749,7 +847,11 @@ class ESIClient(ESIClientStub):
|
|
|
749
847
|
if tag == "_":
|
|
750
848
|
return self.api._operationindex
|
|
751
849
|
|
|
752
|
-
|
|
850
|
+
# convert pythonic Planetary_Interaction to Planetary Interaction
|
|
851
|
+
if "_" in tag:
|
|
852
|
+
tag = tag.replace("_", " ")
|
|
853
|
+
|
|
854
|
+
if tag in set(self.api._operationindex._tags.keys()):
|
|
753
855
|
return ESITag(self.api._operationindex._tags[tag], self.api)
|
|
754
856
|
|
|
755
857
|
raise AttributeError(
|
|
@@ -772,7 +874,11 @@ class ESIClientAsync(ESIClientStub):
|
|
|
772
874
|
if tag == "_":
|
|
773
875
|
return self.api._operationindex
|
|
774
876
|
|
|
775
|
-
|
|
877
|
+
# convert pythonic Planetary_Interaction to Planetary Interaction
|
|
878
|
+
if "_" in tag:
|
|
879
|
+
tag = tag.replace("_", " ")
|
|
880
|
+
|
|
881
|
+
if tag in set(self.api._operationindex._tags.keys()):
|
|
776
882
|
return ESITagAsync(self.api._operationindex._tags[tag], self.api)
|
|
777
883
|
|
|
778
884
|
raise AttributeError(
|
|
@@ -784,7 +890,7 @@ class ESIClientAsync(ESIClientStub):
|
|
|
784
890
|
class ESIClientProvider:
|
|
785
891
|
"""Class for providing a single ESI client instance for a whole app
|
|
786
892
|
Args:
|
|
787
|
-
compatibility_date (str): The compatibility date for the ESI client.
|
|
893
|
+
compatibility_date (str | date): The compatibility date for the ESI client.
|
|
788
894
|
ua_appname (str): Name of the App for generating a User-Agent,
|
|
789
895
|
ua_version (str): Version of the App for generating a User-Agent,
|
|
790
896
|
ua_url (str, Optional): URL To the Source Code or Documentation for generating a User-Agent,
|
|
@@ -795,9 +901,12 @@ class ESIClientProvider:
|
|
|
795
901
|
client_async(): ESIClientAsync
|
|
796
902
|
"""
|
|
797
903
|
|
|
904
|
+
_client: ESIClient | None = None
|
|
905
|
+
_client_async: ESIClientAsync | None = None
|
|
906
|
+
|
|
798
907
|
def __init__(
|
|
799
908
|
self,
|
|
800
|
-
compatibility_date: str,
|
|
909
|
+
compatibility_date: str | date,
|
|
801
910
|
ua_appname: str,
|
|
802
911
|
ua_version: str,
|
|
803
912
|
ua_url: str | None = None,
|
|
@@ -805,7 +914,10 @@ class ESIClientProvider:
|
|
|
805
914
|
tenant: str = "tranquility",
|
|
806
915
|
**kwargs
|
|
807
916
|
) -> None:
|
|
808
|
-
|
|
917
|
+
if type(compatibility_date) is date:
|
|
918
|
+
self._compatibility_date = self._date_to_string(compatibility_date)
|
|
919
|
+
else:
|
|
920
|
+
self._compatibility_date = compatibility_date
|
|
809
921
|
self._ua_appname = ua_appname
|
|
810
922
|
self._ua_version = ua_version
|
|
811
923
|
self._ua_url = ua_url
|
|
@@ -841,5 +953,11 @@ class ESIClientProvider:
|
|
|
841
953
|
self._client_async = ESIClientAsync(api)
|
|
842
954
|
return self._client_async
|
|
843
955
|
|
|
956
|
+
@classmethod
|
|
957
|
+
def _date_to_string(cls, compatibility_date: date) -> str:
|
|
958
|
+
"""Turns a date object in a compatibility_date string"""
|
|
959
|
+
return f"{compatibility_date.year}-{compatibility_date.month:02}-{compatibility_date.day:02}"
|
|
960
|
+
|
|
961
|
+
|
|
844
962
|
def __str__(self) -> str:
|
|
845
963
|
return "ESIClientProvider"
|