django-esi 8.0.0a3__py3-none-any.whl → 8.0.0a4__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.
- {django_esi-8.0.0a3.dist-info → django_esi-8.0.0a4.dist-info}/METADATA +1 -1
- {django_esi-8.0.0a3.dist-info → django_esi-8.0.0a4.dist-info}/RECORD +40 -29
- esi/__init__.py +2 -2
- esi/aiopenapi3/plugins.py +13 -2
- esi/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- esi/locale/cs_CZ/LC_MESSAGES/django.po +53 -0
- esi/locale/de/LC_MESSAGES/django.mo +0 -0
- esi/locale/de/LC_MESSAGES/django.po +10 -9
- esi/locale/en/LC_MESSAGES/django.mo +0 -0
- esi/locale/en/LC_MESSAGES/django.po +3 -3
- esi/locale/es/LC_MESSAGES/django.mo +0 -0
- esi/locale/es/LC_MESSAGES/django.po +12 -10
- esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
- esi/locale/fr_FR/LC_MESSAGES/django.po +18 -10
- esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
- esi/locale/it_IT/LC_MESSAGES/django.po +12 -10
- esi/locale/ja/LC_MESSAGES/django.mo +0 -0
- esi/locale/ja/LC_MESSAGES/django.po +10 -9
- esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
- esi/locale/ko_KR/LC_MESSAGES/django.po +10 -9
- esi/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
- esi/locale/nl_NL/LC_MESSAGES/django.po +53 -0
- esi/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
- esi/locale/pl_PL/LC_MESSAGES/django.po +53 -0
- esi/locale/ru/LC_MESSAGES/django.mo +0 -0
- esi/locale/ru/LC_MESSAGES/django.po +13 -10
- esi/locale/sk/LC_MESSAGES/django.mo +0 -0
- esi/locale/sk/LC_MESSAGES/django.po +55 -0
- esi/locale/uk/LC_MESSAGES/django.mo +0 -0
- esi/locale/uk/LC_MESSAGES/django.po +57 -0
- esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- esi/locale/zh_Hans/LC_MESSAGES/django.po +10 -9
- esi/management/commands/generate_esi_stubs.py +11 -31
- esi/models.py +1 -1
- esi/openapi_clients.py +161 -44
- esi/stubs.pyi +388 -388
- esi/tests/test_openapi.json +264 -0
- esi/tests/test_openapi.py +248 -1
- {django_esi-8.0.0a3.dist-info → django_esi-8.0.0a4.dist-info}/WHEEL +0 -0
- {django_esi-8.0.0a3.dist-info → django_esi-8.0.0a4.dist-info}/licenses/LICENSE +0 -0
esi/openapi_clients.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import pathlib
|
|
2
3
|
import warnings
|
|
3
4
|
from datetime import datetime, date
|
|
4
5
|
from hashlib import md5
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
|
-
from aiopenapi3 import OpenAPI
|
|
8
|
+
from aiopenapi3 import OpenAPI, FileSystemLoader
|
|
8
9
|
from aiopenapi3._types import ResponseDataType, ResponseHeadersType
|
|
9
10
|
from aiopenapi3.errors import HTTPServerError as base_HTTPServerError
|
|
10
11
|
from aiopenapi3.errors import HTTPClientError as base_HTTPClientError
|
|
@@ -18,10 +19,11 @@ from tenacity import (
|
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
from django.core.cache import cache
|
|
22
|
+
from django.utils.text import slugify
|
|
21
23
|
|
|
22
24
|
from esi import app_settings
|
|
23
25
|
from esi.exceptions import HTTPClientError, HTTPServerError, HTTPNotModified
|
|
24
|
-
from esi.aiopenapi3.plugins import Add304ContentType, PatchCompatibilityDatePlugin, Trim204ContentType
|
|
26
|
+
from esi.aiopenapi3.plugins import Add304ContentType, DjangoESIInit, PatchCompatibilityDatePlugin, Trim204ContentType
|
|
25
27
|
from esi.exceptions import ESIErrorLimitException
|
|
26
28
|
from esi.models import Token
|
|
27
29
|
from esi.stubs import ESIClientStub
|
|
@@ -30,6 +32,8 @@ from . import __title__, __url__, __version__
|
|
|
30
32
|
|
|
31
33
|
logger = logging.getLogger(__name__)
|
|
32
34
|
|
|
35
|
+
ETAG_EXPIRY = 60*60*24*7 # 7 days
|
|
36
|
+
|
|
33
37
|
|
|
34
38
|
def _time_to_expiry(expires_header: str) -> int:
|
|
35
39
|
"""Calculate cache TTL from Expires header
|
|
@@ -81,9 +85,19 @@ async def http_retry_async() -> AsyncRetrying:
|
|
|
81
85
|
)
|
|
82
86
|
|
|
83
87
|
|
|
88
|
+
def _load_plugins(app_name):
|
|
89
|
+
"""Load the plugins to make ESI work with this lib.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
app_name (str): app name to use for internal etags
|
|
93
|
+
"""
|
|
94
|
+
return [PatchCompatibilityDatePlugin(), Trim204ContentType(), Add304ContentType(), DjangoESIInit(app_name)]
|
|
95
|
+
|
|
96
|
+
|
|
84
97
|
def _load_aiopenapi_client_sync(
|
|
85
98
|
spec_url: str,
|
|
86
99
|
compatibility_date: str,
|
|
100
|
+
app_name: str,
|
|
87
101
|
user_agent: str,
|
|
88
102
|
tenant: str,
|
|
89
103
|
spec_file: str | None = None) -> OpenAPI:
|
|
@@ -92,6 +106,7 @@ def _load_aiopenapi_client_sync(
|
|
|
92
106
|
Args:
|
|
93
107
|
spec_url (str): _description_
|
|
94
108
|
compatibility_date (str): _description_
|
|
109
|
+
app_name (str): _description_
|
|
95
110
|
user_agent (str): _description_
|
|
96
111
|
tenant (str): _description_
|
|
97
112
|
spec_file (str | None, optional): _description_. Defaults to None.
|
|
@@ -123,21 +138,23 @@ def _load_aiopenapi_client_sync(
|
|
|
123
138
|
url=spec_url,
|
|
124
139
|
path=spec_file,
|
|
125
140
|
session_factory=session_factory,
|
|
141
|
+
loader=FileSystemLoader(pathlib.Path(spec_file)),
|
|
126
142
|
use_operation_tags=True,
|
|
127
|
-
plugins=
|
|
143
|
+
plugins=_load_plugins(app_name)
|
|
128
144
|
)
|
|
129
145
|
else:
|
|
130
146
|
return OpenAPI.load_sync(
|
|
131
147
|
url=spec_url,
|
|
132
148
|
session_factory=session_factory,
|
|
133
149
|
use_operation_tags=True,
|
|
134
|
-
plugins=
|
|
150
|
+
plugins=_load_plugins(app_name)
|
|
135
151
|
)
|
|
136
152
|
|
|
137
153
|
|
|
138
154
|
async def _load_aiopenapi_client_async(
|
|
139
155
|
spec_url: str,
|
|
140
156
|
compatibility_date: str,
|
|
157
|
+
app_name: str,
|
|
141
158
|
user_agent: str,
|
|
142
159
|
tenant: str,
|
|
143
160
|
spec_file: str | None = None) -> OpenAPI:
|
|
@@ -179,18 +196,18 @@ async def _load_aiopenapi_client_async(
|
|
|
179
196
|
path=spec_file,
|
|
180
197
|
session_factory=session_factory,
|
|
181
198
|
use_operation_tags=True,
|
|
182
|
-
plugins=
|
|
199
|
+
plugins=_load_plugins(app_name)
|
|
183
200
|
)
|
|
184
201
|
else:
|
|
185
202
|
return await OpenAPI.load_async(
|
|
186
203
|
url=spec_url,
|
|
187
204
|
session_factory=session_factory,
|
|
188
205
|
use_operation_tags=True,
|
|
189
|
-
plugins=
|
|
206
|
+
plugins=_load_plugins(app_name)
|
|
190
207
|
)
|
|
191
208
|
|
|
192
209
|
|
|
193
|
-
def _build_user_agent(ua_appname: str, ua_version: str, ua_url: str | None) -> str:
|
|
210
|
+
def _build_user_agent(ua_appname: str, ua_version: str, ua_url: str | None = None) -> str:
|
|
194
211
|
"""
|
|
195
212
|
AppName/1.2.3 (foo@example.com; +https://gitlab.com/) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi)
|
|
196
213
|
Contact Email will be inserted from app_settings.
|
|
@@ -231,7 +248,7 @@ def esi_client_factory_sync(
|
|
|
231
248
|
"""
|
|
232
249
|
user_agent = _build_user_agent(ua_appname, ua_version, ua_url)
|
|
233
250
|
spec_url = _get_spec_url()
|
|
234
|
-
return _load_aiopenapi_client_sync(spec_url, compatibility_date, user_agent, tenant, spec_file)
|
|
251
|
+
return _load_aiopenapi_client_sync(spec_url, compatibility_date, ua_appname, user_agent, tenant, spec_file)
|
|
235
252
|
|
|
236
253
|
|
|
237
254
|
async def esi_client_factory_async(
|
|
@@ -253,11 +270,11 @@ async def esi_client_factory_async(
|
|
|
253
270
|
"""
|
|
254
271
|
user_agent = _build_user_agent(ua_appname, ua_version, ua_url)
|
|
255
272
|
spec_url = _get_spec_url()
|
|
256
|
-
return await _load_aiopenapi_client_async(spec_url, compatibility_date, user_agent, tenant, spec_file)
|
|
273
|
+
return await _load_aiopenapi_client_async(spec_url, compatibility_date, ua_appname, user_agent, tenant, spec_file)
|
|
257
274
|
|
|
258
275
|
|
|
259
276
|
class BaseEsiOperation():
|
|
260
|
-
def __init__(self, operation, api) -> None:
|
|
277
|
+
def __init__(self, operation, api: OpenAPI) -> None:
|
|
261
278
|
self.method, self.url, self.operation, self.extra = operation
|
|
262
279
|
self.api = api
|
|
263
280
|
self.token: Token | None = None
|
|
@@ -320,16 +337,24 @@ class BaseEsiOperation():
|
|
|
320
337
|
|
|
321
338
|
return normalized
|
|
322
339
|
|
|
340
|
+
def _etag_key(self) -> str:
|
|
341
|
+
"""Generate a key name used to cache etag responses based on app_name and cache_key
|
|
342
|
+
Returns:
|
|
343
|
+
str: Key
|
|
344
|
+
"""
|
|
345
|
+
# ignore the token this will break the cache
|
|
346
|
+
return f"{slugify(self.api.app_name)}_etag_{self._cache_key()}"
|
|
347
|
+
|
|
323
348
|
def _cache_key(self) -> str:
|
|
324
349
|
"""Generate a key name used to cache responses based on method, url, args, kwargs
|
|
325
350
|
Returns:
|
|
326
|
-
str: Key
|
|
351
|
+
str: Key
|
|
327
352
|
"""
|
|
328
353
|
# ignore the token this will break the cache
|
|
329
354
|
ignore_keys = [
|
|
330
355
|
"token",
|
|
331
356
|
]
|
|
332
|
-
_kwargs = {
|
|
357
|
+
_kwargs = {key: value for key, value in self._kwargs.items() if key not in ignore_keys}
|
|
333
358
|
data = (self.method + self.url + str(self._args) + str(_kwargs)).encode('utf-8')
|
|
334
359
|
str_hash = md5(data).hexdigest() # nosec B303
|
|
335
360
|
return f'esi_{str_hash}'
|
|
@@ -376,6 +401,9 @@ class BaseEsiOperation():
|
|
|
376
401
|
tuple[ResponseHeadersType | None, Any, Response | None]: The cached response,
|
|
377
402
|
or None if not found or expired
|
|
378
403
|
"""
|
|
404
|
+
if not app_settings.ESI_CACHE_RESPONSE:
|
|
405
|
+
return None, None, None
|
|
406
|
+
|
|
379
407
|
try:
|
|
380
408
|
cached_response = cache.get(cache_key)
|
|
381
409
|
except Exception as e:
|
|
@@ -394,6 +422,8 @@ class BaseEsiOperation():
|
|
|
394
422
|
# check if etag is same before building models from cache
|
|
395
423
|
if etag:
|
|
396
424
|
if cached_response.headers.get('Expires') == etag:
|
|
425
|
+
# refresh/store the etag's TTL
|
|
426
|
+
self._store_etag(cached_response.headers)
|
|
397
427
|
raise HTTPNotModified(
|
|
398
428
|
status_code=304,
|
|
399
429
|
headers=cached_response.headers
|
|
@@ -405,8 +435,23 @@ class BaseEsiOperation():
|
|
|
405
435
|
|
|
406
436
|
return None, None, None
|
|
407
437
|
|
|
438
|
+
def _store_etag(self, headers: dict):
|
|
439
|
+
"""
|
|
440
|
+
Store response etag in cache for 7 days
|
|
441
|
+
"""
|
|
442
|
+
if "ETag" in headers:
|
|
443
|
+
cache.set(self._etag_key(), headers["ETag"], timeout=ETAG_EXPIRY)
|
|
444
|
+
|
|
445
|
+
def _clear_etag(self):
|
|
446
|
+
""" Delete the cached etag for this operation.
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
cache.delete(self._etag_key())
|
|
450
|
+
except Exception as e:
|
|
451
|
+
logger.error(f"Failed to delete etag {e}", exc_info=True)
|
|
452
|
+
|
|
408
453
|
def _store_cache(self, cache_key: str, response) -> None:
|
|
409
|
-
""" Store the response in cache
|
|
454
|
+
""" Store the response in cache for expiry TTL.
|
|
410
455
|
Args:
|
|
411
456
|
cache_key (str): The cache key to store the response under
|
|
412
457
|
response (Response): The response object to cache
|
|
@@ -414,9 +459,6 @@ class BaseEsiOperation():
|
|
|
414
459
|
if not app_settings.ESI_CACHE_RESPONSE:
|
|
415
460
|
return
|
|
416
461
|
|
|
417
|
-
if "ETag" in response.headers:
|
|
418
|
-
cache.set(f"{cache_key}_etag", response.headers["ETag"])
|
|
419
|
-
|
|
420
462
|
expires = response.headers.get("Expires")
|
|
421
463
|
ttl = _time_to_expiry(expires) if expires else 0
|
|
422
464
|
if ttl > 0:
|
|
@@ -425,6 +467,14 @@ class BaseEsiOperation():
|
|
|
425
467
|
except Exception as e:
|
|
426
468
|
logger.error(f"Failed to cache {e}", exc_info=True)
|
|
427
469
|
|
|
470
|
+
def _clear_cache(self):
|
|
471
|
+
""" Delete the cached data for this operation.
|
|
472
|
+
"""
|
|
473
|
+
try:
|
|
474
|
+
cache.delete(self._cache_key())
|
|
475
|
+
except Exception as e:
|
|
476
|
+
logger.error(f"Failed to delete cache {e}", exc_info=True)
|
|
477
|
+
|
|
428
478
|
def _validate_token_scopes(self, token: Token) -> None:
|
|
429
479
|
"""Validate that the token provided has the required scopes for this ESI operation.
|
|
430
480
|
"""
|
|
@@ -484,8 +534,9 @@ class EsiOperation(BaseEsiOperation):
|
|
|
484
534
|
|
|
485
535
|
def result(
|
|
486
536
|
self,
|
|
487
|
-
|
|
537
|
+
use_etag: bool = True,
|
|
488
538
|
return_response: bool = False,
|
|
539
|
+
force_refresh: bool = False,
|
|
489
540
|
use_cache: bool = True,
|
|
490
541
|
**extra) -> tuple[Any, Response] | Any:
|
|
491
542
|
"""Executes the request and returns the response from ESI for the current operation.
|
|
@@ -499,9 +550,14 @@ class EsiOperation(BaseEsiOperation):
|
|
|
499
550
|
self.body = self._extract_body_param()
|
|
500
551
|
parameters = self._kwargs | extra
|
|
501
552
|
cache_key = self._cache_key()
|
|
502
|
-
etag_key =
|
|
553
|
+
etag_key = self._etag_key()
|
|
554
|
+
etag = None
|
|
503
555
|
|
|
504
|
-
if
|
|
556
|
+
if force_refresh:
|
|
557
|
+
self._clear_cache()
|
|
558
|
+
self._clear_etag()
|
|
559
|
+
|
|
560
|
+
if use_etag:
|
|
505
561
|
etag = cache.get(etag_key)
|
|
506
562
|
|
|
507
563
|
headers, data, response = self._get_cache(cache_key, etag=etag) if use_cache else (None, None, None)
|
|
@@ -524,13 +580,6 @@ class EsiOperation(BaseEsiOperation):
|
|
|
524
580
|
reset = response.headers.get("X-RateLimit-Reset", None)
|
|
525
581
|
cache.set("esi_error_limit_reset", reset, timeout=reset)
|
|
526
582
|
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
583
|
|
|
535
584
|
# Shim our exceptions into Django-ESI
|
|
536
585
|
except base_HTTPServerError as e:
|
|
@@ -549,17 +598,23 @@ class EsiOperation(BaseEsiOperation):
|
|
|
549
598
|
|
|
550
599
|
# Throw a 304 exception for catching.
|
|
551
600
|
if response.status_code == 304:
|
|
601
|
+
# refresh/store the etag's TTL
|
|
602
|
+
self._store_etag(response.headers)
|
|
552
603
|
raise HTTPNotModified(
|
|
553
604
|
status_code=304,
|
|
554
605
|
headers=response.headers
|
|
555
606
|
)
|
|
556
607
|
|
|
608
|
+
# last step store cache we dont want to catch the 304 `None` resonses
|
|
609
|
+
self._store_cache(cache_key, response)
|
|
610
|
+
|
|
557
611
|
return (data, response) if return_response else data
|
|
558
612
|
|
|
559
613
|
def results(
|
|
560
614
|
self,
|
|
561
|
-
|
|
615
|
+
use_etag: bool = True,
|
|
562
616
|
return_response: bool = False,
|
|
617
|
+
force_refresh: bool = False,
|
|
563
618
|
use_cache: bool = True,
|
|
564
619
|
**extra) -> tuple[list[Any], Response | Any | None] | list[Any]:
|
|
565
620
|
all_results: list[Any] = []
|
|
@@ -575,7 +630,12 @@ class EsiOperation(BaseEsiOperation):
|
|
|
575
630
|
total_pages = 1
|
|
576
631
|
while current_page <= total_pages:
|
|
577
632
|
self._kwargs["page"] = current_page
|
|
578
|
-
data, response = self.result(
|
|
633
|
+
data, response = self.result(
|
|
634
|
+
use_etag=use_etag,
|
|
635
|
+
return_response=True,
|
|
636
|
+
force_refresh=force_refresh,
|
|
637
|
+
**extra
|
|
638
|
+
)
|
|
579
639
|
last_response = response
|
|
580
640
|
all_results.extend(data if isinstance(data, list) else [data])
|
|
581
641
|
total_pages = int(response.headers.get("X-Pages", 1))
|
|
@@ -594,7 +654,13 @@ class EsiOperation(BaseEsiOperation):
|
|
|
594
654
|
else:
|
|
595
655
|
cursor_param = "after"
|
|
596
656
|
while True:
|
|
597
|
-
data, response = self.result(
|
|
657
|
+
data, response = self.result(
|
|
658
|
+
use_etag=use_etag,
|
|
659
|
+
return_response=True,
|
|
660
|
+
force_refresh=force_refresh,
|
|
661
|
+
use_cache=use_cache,
|
|
662
|
+
**params
|
|
663
|
+
)
|
|
598
664
|
last_response = response
|
|
599
665
|
if not data:
|
|
600
666
|
break
|
|
@@ -605,7 +671,13 @@ class EsiOperation(BaseEsiOperation):
|
|
|
605
671
|
params[cursor_param] = cursor_token
|
|
606
672
|
|
|
607
673
|
else:
|
|
608
|
-
data, response = self.result(
|
|
674
|
+
data, response = self.result(
|
|
675
|
+
use_etag=use_etag,
|
|
676
|
+
return_response=True,
|
|
677
|
+
force_refresh=force_refresh,
|
|
678
|
+
use_cache=use_cache,
|
|
679
|
+
**extra
|
|
680
|
+
)
|
|
609
681
|
all_results.extend(data if isinstance(data, list) else [data])
|
|
610
682
|
last_response = response
|
|
611
683
|
|
|
@@ -614,7 +686,8 @@ class EsiOperation(BaseEsiOperation):
|
|
|
614
686
|
def results_localized(
|
|
615
687
|
self,
|
|
616
688
|
languages: list[str] | str | None = None,
|
|
617
|
-
**kwargs
|
|
689
|
+
**kwargs
|
|
690
|
+
) -> dict[str, list[Any]]:
|
|
618
691
|
"""Executes the request and returns the response from ESI for all default languages and pages (if any).
|
|
619
692
|
Args:
|
|
620
693
|
languages: (list[str], str, optional) language(s) to return instead of default languages
|
|
@@ -707,17 +780,37 @@ class EsiOperationAsync(BaseEsiOperation):
|
|
|
707
780
|
|
|
708
781
|
if not response:
|
|
709
782
|
logger.debug(f"Cache Miss {self.url}")
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
#
|
|
719
|
-
|
|
720
|
-
|
|
783
|
+
try:
|
|
784
|
+
headers, data, response = await self._make_request(parameters, etag)
|
|
785
|
+
if response.status_code == 420:
|
|
786
|
+
reset = response.headers.get("X-RateLimit-Reset", None)
|
|
787
|
+
cache.set("esi_error_limit_reset", reset, timeout=reset)
|
|
788
|
+
raise ESIErrorLimitException(reset=reset)
|
|
789
|
+
self._store_cache(cache_key, response)
|
|
790
|
+
self._store_etag(response.headers)
|
|
791
|
+
# Shim our exceptions into Django-ESI
|
|
792
|
+
except base_HTTPServerError as e:
|
|
793
|
+
raise HTTPServerError(
|
|
794
|
+
status_code=e.status_code,
|
|
795
|
+
headers=e.headers,
|
|
796
|
+
data=e.data
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
except base_HTTPClientError as e:
|
|
800
|
+
raise HTTPClientError(
|
|
801
|
+
status_code=e.status_code,
|
|
802
|
+
headers=e.headers,
|
|
803
|
+
data=e.data
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
# Throw a 304 exception for catching.
|
|
807
|
+
if response.status_code == 304:
|
|
808
|
+
# refresh/store the etag's TTL
|
|
809
|
+
self._store_etag(response.headers)
|
|
810
|
+
raise HTTPNotModified(
|
|
811
|
+
status_code=304,
|
|
812
|
+
headers=response.headers
|
|
813
|
+
)
|
|
721
814
|
|
|
722
815
|
return (data, response) if return_response else data
|
|
723
816
|
|
|
@@ -838,6 +931,7 @@ class ESIClient(ESIClientStub):
|
|
|
838
931
|
Base ESI Client, provides access to Tags Assets, Characters, etc.
|
|
839
932
|
or Raw aiopenapi3 via sad smiley ._.
|
|
840
933
|
"""
|
|
934
|
+
|
|
841
935
|
def __init__(self, api: OpenAPI) -> None:
|
|
842
936
|
self.api = api
|
|
843
937
|
self._tags = set(api._operationindex._tags.keys())
|
|
@@ -859,12 +953,36 @@ class ESIClient(ESIClientStub):
|
|
|
859
953
|
f"Available tags: {', '.join(sorted(self._tags))}"
|
|
860
954
|
)
|
|
861
955
|
|
|
956
|
+
def purge_all_etags(self):
|
|
957
|
+
""" Delete all stored etags from the cache for this application
|
|
958
|
+
|
|
959
|
+
TODO: consider making this more config agnostic
|
|
960
|
+
"""
|
|
961
|
+
try:
|
|
962
|
+
# new lib
|
|
963
|
+
from django_redis import get_redis_connection
|
|
964
|
+
_client = get_redis_connection("default")
|
|
965
|
+
except (NotImplementedError, ModuleNotFoundError):
|
|
966
|
+
# old lib
|
|
967
|
+
from django.core.cache import caches
|
|
968
|
+
default_cache = caches['default']
|
|
969
|
+
_client = default_cache.get_master_client()
|
|
970
|
+
|
|
971
|
+
keys = _client.keys(f":?:{slugify(self.api.app_name)}_etag_*")
|
|
972
|
+
if keys:
|
|
973
|
+
deleted = _client.delete(*keys)
|
|
974
|
+
|
|
975
|
+
logger.info(f"Deleted {deleted} etag keys")
|
|
976
|
+
|
|
977
|
+
return deleted
|
|
978
|
+
|
|
862
979
|
|
|
863
980
|
class ESIClientAsync(ESIClientStub):
|
|
864
981
|
"""
|
|
865
982
|
Async Base ESI Client, provides access to Tags Assets, Characters, etc.
|
|
866
983
|
or Raw aiopenapi3 via sad smiley ._.
|
|
867
984
|
"""
|
|
985
|
+
|
|
868
986
|
def __init__(self, api: OpenAPI) -> None:
|
|
869
987
|
self.api = api
|
|
870
988
|
self._tags = set(api._operationindex._tags.keys())
|
|
@@ -958,6 +1076,5 @@ class ESIClientProvider:
|
|
|
958
1076
|
"""Turns a date object in a compatibility_date string"""
|
|
959
1077
|
return f"{compatibility_date.year}-{compatibility_date.month:02}-{compatibility_date.day:02}"
|
|
960
1078
|
|
|
961
|
-
|
|
962
1079
|
def __str__(self) -> str:
|
|
963
|
-
return "ESIClientProvider"
|
|
1080
|
+
return f"ESIClientProvider - {self._ua_appname} ({self._ua_version})"
|