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
esi/stubs.pyi CHANGED
@@ -2100,18 +2100,18 @@ class GetUniverseSchematicsSchematicIdOperation(EsiOperation):
2100
2100
  ...
2101
2101
 
2102
2102
 
2103
- class GetRouteOriginDestinationOperation(EsiOperation):
2103
+ class PostRouteOperation(EsiOperation):
2104
2104
  """EsiOperation, use result(), results() or results_localized()"""
2105
- def result(self, use_etag: bool = True, return_response: bool = False, force_refresh: bool = False, use_cache: bool = True, **extra) -> list[int]:
2106
- """Get the systems between origin and destination"""
2105
+ def result(self, use_etag: bool = True, return_response: bool = False, force_refresh: bool = False, use_cache: bool = True, **extra) -> Any:
2106
+ """Calculate the systems between the given origin and destination."""
2107
2107
  ...
2108
2108
 
2109
- def results(self, use_etag: bool = True, return_response: bool = False, force_refresh: bool = False, use_cache: bool = True, **extra) -> list[int]:
2110
- """Get the systems between origin and destination"""
2109
+ def results(self, use_etag: bool = True, return_response: bool = False, force_refresh: bool = False, use_cache: bool = True, **extra) -> list[Any]:
2110
+ """Calculate the systems between the given origin and destination."""
2111
2111
  ...
2112
2112
 
2113
- def results_localized(self, languages: list[str] | str | None = None, **extra) -> dict[str, list[int]]:
2114
- """Get the systems between origin and destination"""
2113
+ def results_localized(self, languages: list[str] | str | None = None, **extra) -> dict[str, list[Any]]:
2114
+ """Calculate the systems between the given origin and destination."""
2115
2115
  ...
2116
2116
 
2117
2117
 
@@ -3581,8 +3581,8 @@ class ESIClientStub:
3581
3581
  Planetary_Interaction: _Planetary_Interaction = _Planetary_Interaction()
3582
3582
 
3583
3583
  class _Routes:
3584
- def GetRouteOriginDestination(self, destination: int, origin: int, avoid: list[Any] | None = ..., connections: list[Any] | None = ..., flag: str | None = ..., Accept_Language: str | None = ..., If_None_Match: str | None = ..., X_Compatibility_Date: str | None = ..., X_Tenant: str | None = ..., **kwargs: Any) -> GetRouteOriginDestinationOperation:
3585
- """Get the systems between origin and destination"""
3584
+ def PostRoute(self, body: dict[str, Any], origin_system_id: int, destination_system_id: int, Accept_Language: str | None = ..., If_None_Match: str | None = ..., X_Compatibility_Date: str | None = ..., X_Tenant: str | None = ..., **kwargs: Any) -> PostRouteOperation:
3585
+ """Calculate the systems between the given origin and destination."""
3586
3586
  ...
3587
3587
 
3588
3588
 
esi/tests/__init__.py CHANGED
@@ -23,10 +23,10 @@ def _generate_token(
23
23
  expires_in: int = 1200,
24
24
  sso_version: int = 2
25
25
  ) -> dict:
26
- from datetime import datetime, timedelta
26
+ import datetime as dt
27
27
 
28
28
  if timestamp_dt is None:
29
- timestamp_dt = datetime.utcnow()
29
+ timestamp_dt = dt.datetime.now(dt.timezone.utc)
30
30
  if scopes is None:
31
31
  scopes = [
32
32
  'esi-mail.read_mail.v1',
@@ -40,7 +40,7 @@ def _generate_token(
40
40
  'timestamp': int(timestamp_dt.timestamp()),
41
41
  'character_id': character_id,
42
42
  'name': character_name,
43
- 'ExpiresOn': _dt_eveformat(timestamp_dt + timedelta(seconds=expires_in)),
43
+ 'ExpiresOn': _dt_eveformat(timestamp_dt + dt.timedelta(seconds=expires_in)),
44
44
  'scp': scopes,
45
45
  'token_type': 'character',
46
46
  'owner': character_owner_hash,
@@ -84,6 +84,32 @@ class SocketAccessError(Exception):
84
84
  """Error raised when a test script accesses the network"""
85
85
 
86
86
 
87
+ class GuardedSocket(socket.socket):
88
+ """A socket subclass that only allows loopback/localhost."""
89
+
90
+ def _address_is_loopback(self, address):
91
+
92
+ host = None
93
+ if isinstance(address, tuple):
94
+ host = address[0]
95
+ else:
96
+ host = address
97
+
98
+ if isinstance(host, bytes):
99
+ host = host.decode()
100
+
101
+ # quick allow by obvious names
102
+ if host in ("localhost", "127.0.0.1", "::1"):
103
+ return True
104
+ else:
105
+ return False
106
+
107
+ def connect(self, address):
108
+ if not self._address_is_loopback(address):
109
+ raise SocketAccessError(f"Attempt to connect to non-localhost address: {address!r}")
110
+ return super().connect(address)
111
+
112
+
87
113
  class NoSocketsTestCase(TestCase):
88
114
  """Variation of Django's TestCase class that prevents any network use.
89
115
 
@@ -98,15 +124,11 @@ class NoSocketsTestCase(TestCase):
98
124
  """
99
125
  @classmethod
100
126
  def setUpClass(cls):
101
- cls.socket_original = socket.socket
102
- socket.socket = cls.guard
127
+ cls._socket_original = socket.socket
128
+ socket.socket = GuardedSocket
103
129
  return super().setUpClass()
104
130
 
105
131
  @classmethod
106
132
  def tearDownClass(cls):
107
- socket.socket = cls.socket_original
108
- return super().tearDownClass()
109
-
110
- @staticmethod
111
- def guard(*args, **kwargs):
112
- raise SocketAccessError("Attempted to access network")
133
+ socket.socket = cls._socket_original
134
+ super().tearDownClass()
esi/tests/test_clients.py CHANGED
@@ -1,4 +1,4 @@
1
- from datetime import datetime, timedelta, timezone
1
+ import datetime
2
2
  import os
3
3
  from unittest.mock import patch, Mock
4
4
  import json
@@ -49,7 +49,7 @@ def _load_json_file(path):
49
49
 
50
50
  class MockResultFuture:
51
51
  def __init__(self):
52
- dt = datetime.utcnow().replace(tzinfo=timezone.utc) + timedelta(seconds=60)
52
+ dt = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=60)
53
53
  self.headers = {"Expires": dt.strftime("%a, %d %b %Y %H:%M:%S %Z")}
54
54
  self.status_code = 200
55
55
  self.text = "dummy"
@@ -57,7 +57,7 @@ class MockResultFuture:
57
57
 
58
58
  class MockResultPast:
59
59
  def __init__(self):
60
- dt = datetime.utcnow().replace(tzinfo=timezone.utc) - timedelta(seconds=60)
60
+ dt = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(seconds=60)
61
61
  self.headers = {"Expires": dt.strftime("%a, %d %b %Y %H:%M:%S %Z")}
62
62
 
63
63
 
@@ -198,7 +198,7 @@ class TestTokenAuthenticator(NoSocketsTestCase):
198
198
  request.headers = dict()
199
199
  request.params = dict()
200
200
 
201
- self.token.created -= timedelta(121)
201
+ self.token.created -= datetime.timedelta(121)
202
202
 
203
203
  x = TokenAuthenticator(token=self.token)
204
204
  request2 = x.apply(request)
@@ -212,7 +212,7 @@ class TestTokenAuthenticator(NoSocketsTestCase):
212
212
  request.headers = dict()
213
213
  request.params = dict()
214
214
 
215
- self.token.created -= timedelta(121)
215
+ self.token.created -= datetime.timedelta(121)
216
216
  self.token.refresh_token = None
217
217
 
218
218
  x = TokenAuthenticator(token=self.token)
@@ -733,7 +733,10 @@ class TestClientResult2(NoSocketsTestCase):
733
733
  # then
734
734
  self.assertTrue(requests_mocker.called)
735
735
  request = requests_mocker.last_request
736
- self.assertEqual(request._request.headers["User-Agent"], "Django-ESI/1.0.0 (email@example.com; +https://gitlab.com/allianceauth/django-esi)")
736
+
737
+ expected_title = 'DjangoEsi'
738
+
739
+ self.assertEqual(request._request.headers["User-Agent"], f"{expected_title}/1.0.0 (email@example.com; +https://gitlab.com/allianceauth/django-esi)")
737
740
 
738
741
  def test_existing_headers(self, requests_mocker):
739
742
  # given
@@ -743,7 +746,10 @@ class TestClientResult2(NoSocketsTestCase):
743
746
  # then
744
747
  self.assertTrue(requests_mocker.called)
745
748
  request = requests_mocker.last_request
746
- self.assertEqual(request._request.headers["User-Agent"], "Django-ESI/1.0.0 (email@example.com; +https://gitlab.com/allianceauth/django-esi)")
749
+
750
+ expected_title = 'DjangoEsi'
751
+
752
+ self.assertEqual(request._request.headers["User-Agent"], f"{expected_title}/1.0.0 (email@example.com; +https://gitlab.com/allianceauth/django-esi)")
747
753
 
748
754
 
749
755
  class TestRequestsClientPlus(NoSocketsTestCase):
@@ -809,7 +815,9 @@ class TestEsiClientFactoryAppText(NoSocketsTestCase):
809
815
  # when
810
816
  operation = client.Status.get_status()
811
817
  # then
812
- self.assertEqual(operation.future.request.headers["User-Agent"], "Django-ESI/1.0.0 (None; +https://gitlab.com/allianceauth/django-esi)")
818
+ expected_app_name = "MyApp"
819
+ expected_title = 'DjangoEsi'
820
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_title}/1.0.0 (None; +https://gitlab.com/allianceauth/django-esi)")
813
821
 
814
822
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
815
823
  def test_defaults_email(self, requests_mocker) -> None:
@@ -821,7 +829,9 @@ class TestEsiClientFactoryAppText(NoSocketsTestCase):
821
829
  # when
822
830
  operation = client.Status.get_status()
823
831
  # then
824
- self.assertEqual(operation.future.request.headers["User-Agent"], "Django-ESI/1.0.0 (email@example.com; +https://gitlab.com/allianceauth/django-esi)")
832
+ expected_app_name = "MyApp"
833
+ expected_title = 'DjangoEsi'
834
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_title}/1.0.0 (email@example.com; +https://gitlab.com/allianceauth/django-esi)")
825
835
 
826
836
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", None)
827
837
  def test_app_text(self, requests_mocker) -> None:
@@ -835,7 +845,9 @@ class TestEsiClientFactoryAppText(NoSocketsTestCase):
835
845
  # when
836
846
  operation = client.Status.get_status()
837
847
  # then
838
- self.assertEqual(operation.future.request.headers["User-Agent"], "my-app v1.0.0 (None) Django-ESI/1.0.0 (+https://gitlab.com/allianceauth/django-esi)",)
848
+ expected_app_name = "MyApp"
849
+ expected_title = 'DjangoEsi'
850
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"my-app v1.0.0 (None) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)",)
839
851
 
840
852
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
841
853
  def test_app_text_with_email(self, requests_mocker):
@@ -848,7 +860,9 @@ class TestEsiClientFactoryAppText(NoSocketsTestCase):
848
860
  # when
849
861
  operation = client.Status.get_status()
850
862
  # then
851
- self.assertEqual(operation.future.request.headers["User-Agent"], "my-app v1.0.0 (email@example.com) Django-ESI/1.0.0 (+https://gitlab.com/allianceauth/django-esi)",)
863
+ expected_app_name = "MyApp"
864
+ expected_title = 'DjangoEsi'
865
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"my-app v1.0.0 (email@example.com) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)",)
852
866
 
853
867
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
854
868
  def test_ua_generator(self, requests_mocker):
@@ -859,7 +873,41 @@ class TestEsiClientFactoryAppText(NoSocketsTestCase):
859
873
  # when
860
874
  operation = client.Status.get_status()
861
875
  # then
862
- self.assertEqual(operation.future.request.headers["User-Agent"], "MyApp/1.0.0 (email@example.com) Django-ESI/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
876
+ expected_app_name = "MyApp"
877
+ expected_title = 'DjangoEsi'
878
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_app_name}/1.0.0 (email@example.com) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
879
+
880
+ @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
881
+ def test_ua_generator_for_appname_with_spaces(self, requests_mocker):
882
+ requests_mocker.register_uri("GET", url="https://esi.evetech.net/_latest/swagger.json", json=self.spec)
883
+ requests_mocker.register_uri("GET", url="https://esi.evetech.net/latest/swagger.json", json=self.spec)
884
+ requests_mocker.register_uri("GET", url="https://esi.evetech.net/v1/status/", json=self.status_response)
885
+ client = esi_client_factory(ua_appname="My App", ua_version="1.0.0")
886
+
887
+ # when
888
+ operation = client.Status.get_status()
889
+
890
+ # then
891
+ expected_app_name = "MyApp"
892
+ expected_title = 'DjangoEsi'
893
+
894
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_app_name}/1.0.0 (email@example.com) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
895
+
896
+ @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
897
+ def test_ua_generator_for_appname_with_hyphens(self, requests_mocker):
898
+ requests_mocker.register_uri("GET", url="https://esi.evetech.net/_latest/swagger.json", json=self.spec)
899
+ requests_mocker.register_uri("GET", url="https://esi.evetech.net/latest/swagger.json", json=self.spec)
900
+ requests_mocker.register_uri("GET", url="https://esi.evetech.net/v1/status/", json=self.status_response)
901
+ client = esi_client_factory(ua_appname="My-App", ua_version="1.0.0")
902
+
903
+ # when
904
+ operation = client.Status.get_status()
905
+
906
+ # then
907
+ expected_app_name = "MyApp"
908
+ expected_title = 'DjangoEsi'
909
+
910
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_app_name}/1.0.0 (email@example.com) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
863
911
 
864
912
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
865
913
  def test_ua_generator_with_url(self, requests_mocker):
@@ -870,7 +918,9 @@ class TestEsiClientFactoryAppText(NoSocketsTestCase):
870
918
  # when
871
919
  operation = client.Status.get_status()
872
920
  # then
873
- self.assertEqual(operation.future.request.headers["User-Agent"], "MyApp/1.0.0 (email@example.com; +https://example.com) Django-ESI/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
921
+ expected_app_name = "MyApp"
922
+ expected_title = 'DjangoEsi'
923
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_app_name}/1.0.0 (email@example.com; +https://example.com) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
874
924
 
875
925
  @patch(MODULE_PATH + ".__title__", "Django-ESI")
876
926
  @patch(MODULE_PATH + ".__version__", "1.0.0")
@@ -897,7 +947,8 @@ class TestEsiClientProviderAppText(NoSocketsTestCase):
897
947
  # when
898
948
  operation = client.Status.get_status()
899
949
  # then
900
- self.assertEqual(operation.future.request.headers["User-Agent"], "Django-ESI/1.0.0 (None; +https://gitlab.com/allianceauth/django-esi)")
950
+ expected_title = 'DjangoEsi'
951
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_title}/1.0.0 (None; +https://gitlab.com/allianceauth/django-esi)")
901
952
 
902
953
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
903
954
  def test_defaults_email(self, requests_mocker) -> None:
@@ -909,7 +960,8 @@ class TestEsiClientProviderAppText(NoSocketsTestCase):
909
960
  # when
910
961
  operation = client.Status.get_status()
911
962
  # then
912
- self.assertEqual(operation.future.request.headers["User-Agent"], "Django-ESI/1.0.0 (email@example.com; +https://gitlab.com/allianceauth/django-esi)")
963
+ expected_title = 'DjangoEsi'
964
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_title}/1.0.0 (email@example.com; +https://gitlab.com/allianceauth/django-esi)")
913
965
 
914
966
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", None)
915
967
  def test_app_text(self, requests_mocker) -> None:
@@ -923,7 +975,8 @@ class TestEsiClientProviderAppText(NoSocketsTestCase):
923
975
  # when
924
976
  operation = client.Status.get_status()
925
977
  # then
926
- self.assertEqual(operation.future.request.headers["User-Agent"], "my-app v1.0.0 (None) Django-ESI/1.0.0 (+https://gitlab.com/allianceauth/django-esi)",)
978
+ expected_title = 'DjangoEsi'
979
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"my-app v1.0.0 (None) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)",)
927
980
 
928
981
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
929
982
  def test_app_text_with_email(self, requests_mocker):
@@ -936,7 +989,8 @@ class TestEsiClientProviderAppText(NoSocketsTestCase):
936
989
  # when
937
990
  operation = client.Status.get_status()
938
991
  # then
939
- self.assertEqual(operation.future.request.headers["User-Agent"], "my-app v1.0.0 (email@example.com) Django-ESI/1.0.0 (+https://gitlab.com/allianceauth/django-esi)",)
992
+ expected_title = 'DjangoEsi'
993
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"my-app v1.0.0 (email@example.com) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)",)
940
994
 
941
995
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
942
996
  def test_ua_generator(self, requests_mocker):
@@ -947,7 +1001,9 @@ class TestEsiClientProviderAppText(NoSocketsTestCase):
947
1001
  # when
948
1002
  operation = client.Status.get_status()
949
1003
  # then
950
- self.assertEqual(operation.future.request.headers["User-Agent"], "MyApp/1.0.0 (email@example.com) Django-ESI/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
1004
+ expected_app_name = "MyApp"
1005
+ expected_title = 'DjangoEsi'
1006
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_app_name}/1.0.0 (email@example.com) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
951
1007
 
952
1008
  @patch(MODULE_PATH + ".app_settings.ESI_USER_CONTACT_EMAIL", "email@example.com")
953
1009
  def test_ua_generator_with_url(self, requests_mocker):
@@ -958,4 +1014,6 @@ class TestEsiClientProviderAppText(NoSocketsTestCase):
958
1014
  # when
959
1015
  operation = client.Status.get_status()
960
1016
  # then
961
- self.assertEqual(operation.future.request.headers["User-Agent"], "MyApp/1.0.0 (email@example.com; +https://example.com) Django-ESI/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
1017
+ expected_app_name = "MyApp"
1018
+ expected_title = 'DjangoEsi'
1019
+ self.assertEqual(operation.future.request.headers["User-Agent"], f"{expected_app_name}/1.0.0 (email@example.com; +https://example.com) {expected_title}/1.0.0 (+https://gitlab.com/allianceauth/django-esi)")
@@ -1,8 +1,10 @@
1
1
  """unit tests for esi decorators"""
2
2
 
3
3
  import logging
4
+ from time import time
4
5
  from unittest.mock import patch, Mock
5
6
 
7
+ from django.core.cache import cache
6
8
  from django.contrib.auth.models import User
7
9
  from django.contrib.auth.views import redirect_to_login
8
10
  from django.contrib.sessions.middleware import SessionMiddleware
@@ -10,8 +12,10 @@ from django.http import HttpResponse
10
12
  from django.test import TestCase, RequestFactory
11
13
 
12
14
  from . import _generate_token, _store_as_Token
15
+ from ..rate_limiting import ESIRateLimitBucket, ESIRateLimits
16
+ from ..exceptions import ESIBucketLimitException
13
17
  from ..decorators import (
14
- _check_callback, tokens_required, token_required, single_use_token
18
+ _check_callback, esi_rate_limiter_bucketed, tokens_required, token_required, single_use_token, wait_for_esi_errorlimit_reset
15
19
  )
16
20
  from ..models import Token, CallbackRedirect
17
21
 
@@ -516,3 +520,59 @@ class TestSingleUseTokenRequired(TestCase):
516
520
  response,
517
521
  self.token
518
522
  )
523
+
524
+
525
+ class TestESIRateLimitDecorator(TestCase):
526
+
527
+ def setUp(self):
528
+ self.bucket = ESIRateLimitBucket(
529
+ "test-bucket",
530
+ 1,
531
+ 5
532
+ )
533
+ cache.clear()
534
+
535
+ def test_raise(self):
536
+ @esi_rate_limiter_bucketed(bucket=self.bucket)
537
+ def my_func():
538
+ return "Pass"
539
+
540
+ _t = time()
541
+ my_func()
542
+ self.assertLess(time() - _t, 1)
543
+ _t = time()
544
+ with self.assertRaises(ESIBucketLimitException):
545
+ my_func()
546
+
547
+ def test_sleep(self):
548
+ @esi_rate_limiter_bucketed(bucket=self.bucket, raise_on_limit=False)
549
+ def my_func():
550
+ return "Pass"
551
+
552
+ _t = time()
553
+ my_func()
554
+ self.assertLess(time() - _t, 1)
555
+ _t = time()
556
+ my_func()
557
+ duration = time() - _t
558
+ self.assertGreater(duration, 5)
559
+
560
+
561
+ class TestESIErrorLimitDecorator(TestCase):
562
+
563
+ def setUp(self):
564
+ cache.clear()
565
+
566
+ def test_sleep(self):
567
+ @wait_for_esi_errorlimit_reset()
568
+ def my_func():
569
+ return "Pass"
570
+
571
+ _t = time()
572
+ my_func()
573
+ self.assertLess(time() - _t, 1)
574
+ cache.set("esi_error_limit_reset", 5, timeout=5)
575
+ _t = time()
576
+ my_func()
577
+ duration = time() - _t
578
+ self.assertGreater(duration, 5)
@@ -122,7 +122,12 @@
122
122
  }
123
123
  },
124
124
  "type": "object"
125
+ },
126
+ "UniverseTypesGet": {
127
+ "items": { "format": "int64", "type": "integer" },
128
+ "type": "array"
125
129
  }
130
+
126
131
  },
127
132
  "securitySchemes": {
128
133
  "OAuth2": {
@@ -217,6 +222,58 @@
217
222
  },
218
223
  "openapi": "3.1.0",
219
224
  "paths": {
225
+ "/universe/types": {
226
+ "get": {
227
+ "description": "Get a list of type ids\n\nThis route expires daily at 11:05",
228
+ "operationId": "GetUniverseTypes",
229
+ "parameters": [
230
+ {
231
+ "in": "query",
232
+ "name": "page",
233
+ "schema": {
234
+ "description": "Which page of results to return.",
235
+ "format": "int32",
236
+ "minimum": 1,
237
+ "type": "integer"
238
+ }
239
+ },
240
+ { "$ref": "#/components/parameters/AcceptLanguage" },
241
+ { "$ref": "#/components/parameters/IfNoneMatch" },
242
+ { "$ref": "#/components/parameters/CompatibilityDate" },
243
+ { "$ref": "#/components/parameters/Tenant" }
244
+ ],
245
+ "responses": {
246
+ "200": {
247
+ "content": {
248
+ "application/json": {
249
+ "schema": { "$ref": "#/components/schemas/UniverseTypesGet" }
250
+ }
251
+ },
252
+ "description": "OK",
253
+ "headers": {
254
+ "Cache-Control": { "$ref": "#/components/headers/CacheControl" },
255
+ "ETag": { "$ref": "#/components/headers/ETag" },
256
+ "Last-Modified": { "$ref": "#/components/headers/LastModified" },
257
+ "X-Pages": {
258
+ "description": "The total number of pages in the result set.",
259
+ "schema": { "format": "int64", "type": "integer" }
260
+ }
261
+ }
262
+ },
263
+ "default": {
264
+ "content": {
265
+ "application/json": {
266
+ "schema": { "$ref": "#/components/schemas/Error" }
267
+ }
268
+ },
269
+ "description": "Error"
270
+ }
271
+ },
272
+ "summary": "Get types",
273
+ "tags": ["Universe"],
274
+ "x-compatibility-date": "2020-01-01"
275
+ }
276
+ },
220
277
  "/status": {
221
278
  "get": {
222
279
  "description": "EVE Server status",
@@ -253,12 +310,18 @@
253
310
  "summary": "Retrieve the uptime and player counts",
254
311
  "tags": ["Status"],
255
312
  "x-cache-age": 30,
256
- "x-compatibility-date": "2020-01-01"
313
+ "x-compatibility-date": "2020-01-01",
314
+ "x-rate-limit": {
315
+ "group": "status",
316
+ "max-tokens": 600,
317
+ "window-size": "15m"
318
+ }
257
319
  }
258
320
  }
259
321
  },
260
322
  "servers": [{ "url": "https://esi.evetech.net" }],
261
323
  "tags": [
262
- { "name": "Status" }
324
+ { "name": "Status" },
325
+ { "name": "Universe" }
263
326
  ]
264
327
  }