django-esi 8.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. django_esi-8.1.0.dist-info/METADATA +93 -0
  2. django_esi-8.1.0.dist-info/RECORD +100 -0
  3. django_esi-8.1.0.dist-info/WHEEL +4 -0
  4. django_esi-8.1.0.dist-info/licenses/LICENSE +674 -0
  5. esi/__init__.py +7 -0
  6. esi/admin.py +42 -0
  7. esi/aiopenapi3/client.py +79 -0
  8. esi/aiopenapi3/plugins.py +224 -0
  9. esi/app_settings.py +112 -0
  10. esi/apps.py +11 -0
  11. esi/checks.py +56 -0
  12. esi/clients.py +657 -0
  13. esi/decorators.py +271 -0
  14. esi/errors.py +22 -0
  15. esi/exceptions.py +51 -0
  16. esi/helpers.py +63 -0
  17. esi/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  18. esi/locale/cs_CZ/LC_MESSAGES/django.po +53 -0
  19. esi/locale/de/LC_MESSAGES/django.mo +0 -0
  20. esi/locale/de/LC_MESSAGES/django.po +58 -0
  21. esi/locale/en/LC_MESSAGES/django.mo +0 -0
  22. esi/locale/en/LC_MESSAGES/django.po +54 -0
  23. esi/locale/es/LC_MESSAGES/django.mo +0 -0
  24. esi/locale/es/LC_MESSAGES/django.po +59 -0
  25. esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  26. esi/locale/fr_FR/LC_MESSAGES/django.po +59 -0
  27. esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  28. esi/locale/it_IT/LC_MESSAGES/django.po +59 -0
  29. esi/locale/ja/LC_MESSAGES/django.mo +0 -0
  30. esi/locale/ja/LC_MESSAGES/django.po +58 -0
  31. esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  32. esi/locale/ko_KR/LC_MESSAGES/django.po +58 -0
  33. esi/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  34. esi/locale/nl_NL/LC_MESSAGES/django.po +53 -0
  35. esi/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  36. esi/locale/pl_PL/LC_MESSAGES/django.po +53 -0
  37. esi/locale/ru/LC_MESSAGES/django.mo +0 -0
  38. esi/locale/ru/LC_MESSAGES/django.po +61 -0
  39. esi/locale/sk/LC_MESSAGES/django.mo +0 -0
  40. esi/locale/sk/LC_MESSAGES/django.po +55 -0
  41. esi/locale/uk/LC_MESSAGES/django.mo +0 -0
  42. esi/locale/uk/LC_MESSAGES/django.po +57 -0
  43. esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  44. esi/locale/zh_Hans/LC_MESSAGES/django.po +58 -0
  45. esi/management/commands/__init__.py +0 -0
  46. esi/management/commands/esi_clear_spec_cache.py +21 -0
  47. esi/management/commands/generate_esi_stubs.py +661 -0
  48. esi/management/commands/migrate_to_ssov2.py +188 -0
  49. esi/managers.py +303 -0
  50. esi/managers.pyi +85 -0
  51. esi/migrations/0001_initial.py +55 -0
  52. esi/migrations/0002_scopes_20161208.py +56 -0
  53. esi/migrations/0003_hide_tokens_from_admin_site.py +23 -0
  54. esi/migrations/0004_remove_unique_access_token.py +18 -0
  55. esi/migrations/0005_remove_token_length_limit.py +23 -0
  56. esi/migrations/0006_remove_url_length_limit.py +18 -0
  57. esi/migrations/0007_fix_mysql_8_migration.py +18 -0
  58. esi/migrations/0008_nullable_refresh_token.py +18 -0
  59. esi/migrations/0009_set_old_tokens_to_sso_v1.py +18 -0
  60. esi/migrations/0010_set_new_tokens_to_sso_v2.py +18 -0
  61. esi/migrations/0011_add_token_indices.py +28 -0
  62. esi/migrations/0012_fix_token_type_choices.py +18 -0
  63. esi/migrations/0013_squashed_0012_fix_token_type_choices.py +57 -0
  64. esi/migrations/__init__.py +0 -0
  65. esi/models.py +349 -0
  66. esi/openapi_clients.py +1225 -0
  67. esi/rate_limiting.py +107 -0
  68. esi/signals.py +21 -0
  69. esi/static/esi/img/EVE_SSO_Login_Buttons_Large_Black.png +0 -0
  70. esi/static/esi/img/EVE_SSO_Login_Buttons_Large_White.png +0 -0
  71. esi/static/esi/img/EVE_SSO_Login_Buttons_Small_Black.png +0 -0
  72. esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png +0 -0
  73. esi/stubs.py +2 -0
  74. esi/stubs.pyi +6807 -0
  75. esi/tasks.py +78 -0
  76. esi/templates/esi/select_token.html +116 -0
  77. esi/templatetags/__init__.py +0 -0
  78. esi/templatetags/scope_tags.py +8 -0
  79. esi/tests/__init__.py +134 -0
  80. esi/tests/client_authed_pilot.py +63 -0
  81. esi/tests/client_public_pilot.py +53 -0
  82. esi/tests/factories.py +47 -0
  83. esi/tests/factories_2.py +60 -0
  84. esi/tests/jwt_factory.py +135 -0
  85. esi/tests/test_checks.py +48 -0
  86. esi/tests/test_clients.py +1019 -0
  87. esi/tests/test_decorators.py +578 -0
  88. esi/tests/test_management_command.py +307 -0
  89. esi/tests/test_managers.py +673 -0
  90. esi/tests/test_models.py +403 -0
  91. esi/tests/test_openapi.json +854 -0
  92. esi/tests/test_openapi.py +1017 -0
  93. esi/tests/test_swagger.json +489 -0
  94. esi/tests/test_swagger_full.json +51112 -0
  95. esi/tests/test_tasks.py +116 -0
  96. esi/tests/test_templatetags.py +22 -0
  97. esi/tests/test_views.py +331 -0
  98. esi/tests/threading_pilot.py +69 -0
  99. esi/urls.py +9 -0
  100. esi/views.py +129 -0
esi/tasks.py ADDED
@@ -0,0 +1,78 @@
1
+ import logging
2
+ from datetime import timedelta
3
+ from math import ceil
4
+
5
+ from celery import shared_task
6
+
7
+ from django.utils import timezone
8
+
9
+ from .models import CallbackRedirect, Token
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ @shared_task
15
+ def cleanup_callbackredirect(max_age=300):
16
+ """
17
+ Delete old :model:`esi.CallbackRedirect` models.
18
+ Accepts a max_age parameter, in seconds (default 300).
19
+ """
20
+ max_age = timezone.now() - timedelta(seconds=max_age)
21
+ logger.debug(
22
+ "Deleting all callback redirects created before %s",
23
+ max_age.strftime("%b %d %Y %H:%M:%S")
24
+ )
25
+ CallbackRedirect.objects.filter(created__lte=max_age).delete()
26
+
27
+
28
+ @shared_task
29
+ def cleanup_token() -> None:
30
+ """
31
+ Delete Orphaned Tokens, then refresh or delete expired :model:`esi.Token` models.
32
+ """
33
+ orphaned_tokens = Token.objects.filter(user__isnull=True)
34
+ if orphaned_tokens.exists():
35
+ logger.info("Deleting %d orphaned tokens.", orphaned_tokens.count())
36
+ orphaned_tokens.delete()
37
+
38
+ expired_tokens = Token.objects.exclude(user__isnull=True).get_expired()
39
+ if expired_tokens.exists():
40
+ logger.info(
41
+ "Triggering bulk refresh of %d expired tokens.", expired_tokens.count()
42
+ )
43
+ for token_pk in (
44
+ expired_tokens.filter(refresh_token__isnull=False)
45
+ .values_list("pk", flat=True)
46
+ ):
47
+ refresh_or_delete_token.apply_async(args=[token_pk], priority=8)
48
+
49
+
50
+ @shared_task
51
+ def cleanup_token_subset(fraction: int = 48) -> None:
52
+ """
53
+ Delete Orphaned Tokens, then refresh or delete a subset of expired :model:`esi.Token` models.
54
+
55
+ This task operates on 1/fraction of the oldest tokens and can be called on a more regular schedule
56
+ """
57
+ orphaned_tokens = Token.objects.filter(user__isnull=True)
58
+ if orphaned_tokens.exists():
59
+ logger.info("Deleting %d orphaned tokens.", orphaned_tokens.count())
60
+ orphaned_tokens.delete()
61
+
62
+ expired_tokens = Token.objects.exclude(user__isnull=True).get_expired()
63
+ expired_tokens_subset = expired_tokens.filter(
64
+ refresh_token__isnull=False
65
+ ).order_by("created")[:ceil(expired_tokens.count() / fraction)]
66
+
67
+ if expired_tokens.exists():
68
+ logger.info(
69
+ f"Triggering bulk refresh of subset/possible {expired_tokens_subset.count()}/{expired_tokens.count()} expired tokens."
70
+ )
71
+ for token_pk in expired_tokens_subset.values_list("pk", flat=True):
72
+ refresh_or_delete_token.apply_async(args=[token_pk], priority=8)
73
+
74
+
75
+ @shared_task
76
+ def refresh_or_delete_token(token_pk: int):
77
+ token = Token.objects.get(pk=token_pk)
78
+ token.refresh_or_delete()
@@ -0,0 +1,116 @@
1
+ {% load i18n %}
2
+ {% load static %}
3
+ {% load scope_tags %}
4
+
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="utf-8">
8
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1">
10
+ <meta name="description" content="">
11
+ <meta name="author" content="">
12
+
13
+ <title>{% translate "ESI Token Selection" %}</title>
14
+
15
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.1/css/bootstrap.min.css" integrity="sha512-T584yQ/tdRR5QwOpfvDfVQUidzfgc2339Lc8uBDtcp/wYu80d7jwBgAxbyMh0a9YM9F8N3tdErpFI8iaGx6x5g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
16
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.slim.min.js" integrity="sha512-6ORWJX/LrnSjBzwefdNUyLCMTIsGoNP6NftMy2UAm1JBm6PRZCO1d7OHBStWpVFZLO+RerTvqX/Z9mBFfCJZ4A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
17
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.1/js/bootstrap.min.js" integrity="sha512-UR25UO94eTnCVwjbXozyeVd6ZqpaAE9naiEUBK/A+QDbfSTQFhPGj5lOR6d8tsgbBk84Ggb5A3EkjsOgPRPcKA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
18
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
19
+
20
+ <style>
21
+ .character-image{
22
+ width: 110px;
23
+ height: auto;
24
+ }
25
+
26
+ @media (min-width: 550px) {
27
+ .character-image {
28
+ width: 175px;
29
+ }
30
+ }
31
+ /* sm */
32
+ @media (min-width: 768px) {
33
+ .character-image {
34
+ width: 210px;
35
+ }
36
+ .sso-card{
37
+ width: auto;
38
+ }
39
+ }
40
+ </style>
41
+ </head>
42
+ <body class="bg-dark">
43
+ <div class="container-fluid mt-5">
44
+ <h3 class="text-light font-weight-light text-center">{% translate "Select Character" %}</h3><br>
45
+ <div class="mt-2">
46
+ <div class="col-lg-10 offset-lg-1 col-md-12" style="padding-left: 0 !important; padding-right: 0 !important;">
47
+ <div class="card text-white bg-secondary">
48
+ <div class="card-header text-center">{% translate "Scopes Requested" %}</div>
49
+ <div class="card-body align-middle text-center">
50
+ {% for scope in scopes %}<span class="badge badge-dark">{{ scope|scope_friendly_name }}</span> {% endfor %}
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ <br>
56
+ <div class="col-lg-10 offset-lg-1 col-md-12 container" style="padding-left: 0 !important; padding-right: 0 !important;">
57
+ <div class="d-flex flex-wrap justify-content-center">
58
+ {% if tokens|length > 14 %}
59
+ <form method="post">
60
+ <div class="card text-white bg-secondary m-2 sso-card">
61
+ <div class="card-header text-center">{% translate "New Character" %}</div>
62
+ <div class="card-body text-center d-none d-sm-block">
63
+ <input type="image" formmethod="post" class="ra-avatar img-responsive character-image rounded-lg"
64
+ src="https://images.evetech.net/characters/1/portrait?size=256">
65
+ </div>
66
+ <div class="card-footer">
67
+ {% csrf_token %}
68
+ <input type="hidden" name="_add" value="True">
69
+ <a href="#" type="submit" class="m-1">
70
+ <input type="image" name="go" src="{% static 'esi/img/EVE_SSO_Login_Buttons_Small_Black.png'%}"
71
+ alt="{% translate "Add Token" %}">
72
+ </a>
73
+ </div>
74
+ </div>
75
+ </form>
76
+ {% endif %}
77
+ {% for token in tokens|dictsort:"character_name" %}
78
+ <form method="post">
79
+ {% csrf_token %}
80
+ <div class="card text-white bg-secondary m-2 mw-50">
81
+ <div class="card-header text-center">{{ token.character_name }}</div>
82
+ <div class="card-body text-center ">
83
+ <input type="image" formmethod="post" class="ra-avatar img-responsive character-image rounded-lg"
84
+ src="https://images.evetech.net/characters/{{ token.character_id }}/portrait?size=256">
85
+ </div>
86
+ <div class="card-footer">
87
+ <input type="hidden" name="_token" value="{{ token.pk }}">
88
+ <button class="btn btn-success w-100" type="submit" title="{% translate "Select" %}" formmethod="post">
89
+ {% translate "Select" %}
90
+ </button>
91
+ </div>
92
+ </div>
93
+ </form>
94
+ {% endfor %}
95
+ <form method="post">
96
+ {% csrf_token %}
97
+ <div class="card text-white bg-secondary m-2 sso-card">
98
+ <div class="card-header text-center">{% translate "New Character" %}</div>
99
+ <div class="card-body text-center d-none d-sm-block">
100
+ <input type="image" formmethod="post" class="ra-avatar img-responsive character-image rounded-lg"
101
+ src="https://images.evetech.net/characters/1/portrait?size=256">
102
+ </div>
103
+ <div class="card-footer">
104
+ <input type="hidden" name="_add" value="True">
105
+ <a href="#" type="submit" class="m-1">
106
+ <input type="image" name="go" src="{% static 'esi/img/EVE_SSO_Login_Buttons_Small_Black.png'%}"
107
+ alt="{% translate "Add Token" %}">
108
+ </a>
109
+ </div>
110
+ </div>
111
+ </form>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </body>
116
+ </html>
File without changes
@@ -0,0 +1,8 @@
1
+ from django.template.defaulttags import register
2
+
3
+ from ..models import Scope
4
+
5
+
6
+ @register.filter()
7
+ def scope_friendly_name(name):
8
+ return Scope._friendly_name(name)
esi/tests/__init__.py ADDED
@@ -0,0 +1,134 @@
1
+ import socket
2
+ from django.test import TestCase
3
+
4
+
5
+ def _dt_eveformat(dt: object) -> str:
6
+ """converts a datetime to a string in eve format
7
+ e.g. '2019-06-25T19:04:44'
8
+ """
9
+ from datetime import datetime
10
+
11
+ dt2 = datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
12
+ return dt2.isoformat()
13
+
14
+
15
+ def _generate_token(
16
+ character_id: int,
17
+ character_name: str,
18
+ access_token: str = 'access_token',
19
+ refresh_token: str = 'refresh_token',
20
+ character_owner_hash: str = 'character_owner_hash',
21
+ scopes=None, # list or string from ccp
22
+ timestamp_dt: object = None,
23
+ expires_in: int = 1200,
24
+ sso_version: int = 2
25
+ ) -> dict:
26
+ import datetime as dt
27
+
28
+ if timestamp_dt is None:
29
+ timestamp_dt = dt.datetime.now(dt.timezone.utc)
30
+ if scopes is None:
31
+ scopes = [
32
+ 'esi-mail.read_mail.v1',
33
+ 'esi-wallet.read_character_wallet.v1',
34
+ 'esi-universe.read_structures.v1'
35
+ ]
36
+ token = {
37
+ 'access_token': access_token,
38
+ 'expires_in': expires_in,
39
+ 'refresh_token': refresh_token,
40
+ 'timestamp': int(timestamp_dt.timestamp()),
41
+ 'character_id': character_id,
42
+ 'name': character_name,
43
+ 'ExpiresOn': _dt_eveformat(timestamp_dt + dt.timedelta(seconds=expires_in)),
44
+ 'scp': scopes,
45
+ 'token_type': 'character',
46
+ 'owner': character_owner_hash,
47
+ 'IntellectualProperty': 'EVE',
48
+ 'sso_version': sso_version
49
+ }
50
+ return token
51
+
52
+
53
+ def _store_as_Token(token: dict, user: object) -> object:
54
+ """Stores a generated token dict as Token object for given user
55
+
56
+ returns Token object
57
+ """
58
+ from ..models import Scope, Token
59
+
60
+ obj = Token.objects.create(
61
+ access_token=token['access_token'],
62
+ refresh_token=token['refresh_token'],
63
+ user=user,
64
+ character_id=token['character_id'],
65
+ character_name=token['name'],
66
+ token_type=token['token_type'],
67
+ character_owner_hash=token['owner'],
68
+ sso_version=token['sso_version']
69
+ )
70
+
71
+ if isinstance(token['scp'], str):
72
+ token['scp'] = [token['scp']]
73
+
74
+ for scope_name in token['scp']:
75
+ scope, _ = Scope.objects.get_or_create(
76
+ name=scope_name
77
+ )
78
+ obj.scopes.add(scope)
79
+
80
+ return obj
81
+
82
+
83
+ class SocketAccessError(Exception):
84
+ """Error raised when a test script accesses the network"""
85
+
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
+
113
+ class NoSocketsTestCase(TestCase):
114
+ """Variation of Django's TestCase class that prevents any network use.
115
+
116
+ Example:
117
+
118
+ .. code-block:: python
119
+
120
+ class TestMyStuff(NoSocketsTestCase):
121
+ def test_should_do_what_i_need(self):
122
+ ...
123
+
124
+ """
125
+ @classmethod
126
+ def setUpClass(cls):
127
+ cls._socket_original = socket.socket
128
+ socket.socket = GuardedSocket
129
+ return super().setUpClass()
130
+
131
+ @classmethod
132
+ def tearDownClass(cls):
133
+ socket.socket = cls._socket_original
134
+ super().tearDownClass()
@@ -0,0 +1,63 @@
1
+ # flake8: noqa
2
+ """script for testing client with live requests to ESI
3
+
4
+ This script is doing an authed request and required at least one valid token to be stored in the database. To create a valid token you can simple run through
5
+ the esi test app once.
6
+
7
+ Run this script directly. Make sure to also set the environment variable
8
+ DJANGO_PROJECT_PATH and DJANGO_SETTINGS_MODULE to match your setup:
9
+
10
+ You can see the result in your main log file of your Django installation.
11
+
12
+ Example:
13
+ export DJANGO_PROJECT_PATH="/home/erik997/dev/python/django-dev/mysite"
14
+ export DJANGO_SETTINGS_MODULE="mysite.settings"
15
+
16
+ """
17
+
18
+ # start django project
19
+ import os
20
+ import sys
21
+
22
+ if not 'DJANGO_PROJECT_PATH' in os.environ:
23
+ print('DJANGO_PROJECT_PATH is not set')
24
+ exit(1)
25
+
26
+ if not 'DJANGO_SETTINGS_MODULE' in os.environ:
27
+ print('DJANGO_SETTINGS_MODULE is not set')
28
+ exit(1)
29
+
30
+ sys.path.insert(0, os.environ['DJANGO_PROJECT_PATH'])
31
+ import django
32
+ django.setup()
33
+
34
+ # normal imports
35
+ import concurrent.futures
36
+ import logging
37
+
38
+ from django.core.cache import cache
39
+ from esi.models import Token
40
+ from esi.clients import EsiClientProvider
41
+
42
+ cache.clear()
43
+ esi = EsiClientProvider()
44
+
45
+ logger = logging.getLogger('__name__')
46
+ logger.level = logging.DEBUG
47
+
48
+
49
+ def main():
50
+ token = Token.objects.filter(scopes__name='esi-characters.read_medals.v1').first()
51
+ response = (
52
+ esi.client.Character
53
+ .get_characters_character_id_medals(
54
+ character_id=token.character_id,
55
+ token=token.valid_access_token()
56
+ )
57
+ .result()
58
+ )
59
+
60
+ if __name__ == '__main__':
61
+ print('Script started...')
62
+ main()
63
+ print('DONE')
@@ -0,0 +1,53 @@
1
+ # flake8: noqa
2
+ """script for testing client with live requests to ESI
3
+
4
+ Run this script directly. Make sure to also set the environment variable
5
+ DJANGO_PROJECT_PATH and DJANGO_SETTINGS_MODULE to match your setup:
6
+
7
+ You can see the result in your main log file of your Django installation.
8
+
9
+ Example:
10
+ export DJANGO_PROJECT_PATH="/home/erik997/dev/python/django-dev/mysite"
11
+ export DJANGO_SETTINGS_MODULE="mysite.settings"
12
+
13
+ """
14
+
15
+ # start django project
16
+ import os
17
+ import sys
18
+
19
+ if not 'DJANGO_PROJECT_PATH' in os.environ:
20
+ print('DJANGO_PROJECT_PATH is not set')
21
+ exit(1)
22
+
23
+ if not 'DJANGO_SETTINGS_MODULE' in os.environ:
24
+ print('DJANGO_SETTINGS_MODULE is not set')
25
+ exit(1)
26
+
27
+ sys.path.insert(0, os.environ['DJANGO_PROJECT_PATH'])
28
+ import django
29
+ django.setup()
30
+
31
+ # normal imports
32
+ import concurrent.futures
33
+ import logging
34
+
35
+ from django.core.cache import cache
36
+ from esi.clients import EsiClientProvider
37
+
38
+ cache.clear()
39
+ esi = EsiClientProvider()
40
+
41
+ logger = logging.getLogger('__name__')
42
+ logger.level = logging.DEBUG
43
+
44
+
45
+ def main():
46
+ # esi.client.Universe.get_universe_types().results()
47
+ o = esi.client.Universe.get_universe_types_type_id(type_id=34562)
48
+ o.results()
49
+
50
+ if __name__ == '__main__':
51
+ print('Script started...')
52
+ main()
53
+ print('DONE')
esi/tests/factories.py ADDED
@@ -0,0 +1,47 @@
1
+ from bravado.exception import (
2
+ HTTPBadGateway,
3
+ HTTPBadRequest,
4
+ HTTPForbidden,
5
+ HTTPGatewayTimeout,
6
+ HTTPInternalServerError,
7
+ HTTPNotFound,
8
+ HTTPServiceUnavailable,
9
+ HTTPUnauthorized,
10
+ )
11
+
12
+
13
+ def create_http_error(http_code: int, text: str = None) -> Exception:
14
+ """Build a HTTP exception for django-esi from given http code."""
15
+ exc_map = {
16
+ 400: HTTPBadRequest,
17
+ 401: HTTPUnauthorized,
18
+ 403: HTTPForbidden,
19
+ 404: HTTPNotFound,
20
+ 500: HTTPInternalServerError,
21
+ 502: HTTPBadGateway,
22
+ 503: HTTPServiceUnavailable,
23
+ 504: HTTPGatewayTimeout,
24
+ }
25
+ try:
26
+ http_exc = exc_map[http_code]
27
+ except KeyError:
28
+ raise NotImplementedError(f"Unknown http code: {http_code}") from None
29
+ if not text:
30
+ text = "Test exception"
31
+ return http_exc(response=BravadoResponseStub(http_code, text))
32
+
33
+
34
+ class BravadoResponseStub:
35
+ """Stub for IncomingResponse in bravado, e.g. for HTTPError exceptions."""
36
+
37
+ def __init__(
38
+ self, status_code, reason="", text="", headers=None, raw_bytes=None
39
+ ) -> None:
40
+ self.status_code = status_code
41
+ self.reason = reason
42
+ self.text = text
43
+ self.headers = headers if headers else dict()
44
+ self.raw_bytes = raw_bytes
45
+
46
+ def __str__(self) -> str:
47
+ return f"{self.status_code} {self.reason}"
@@ -0,0 +1,60 @@
1
+ """Factories for test objects using factory boy."""
2
+
3
+ import factory
4
+ import factory.fuzzy
5
+
6
+ from esi.models import Token, Scope, CallbackRedirect
7
+
8
+
9
+ class UserFactory(factory.django.DjangoModelFactory):
10
+ class Meta:
11
+ model = "auth.User"
12
+ django_get_or_create = ("username",)
13
+ exclude = ("_generated_name",)
14
+
15
+ _generated_name = factory.Faker("name")
16
+ username = factory.LazyAttribute(lambda obj: obj._generated_name.replace(" ", "_"))
17
+ first_name = factory.LazyAttribute(lambda obj: obj._generated_name.split(" ")[0])
18
+ last_name = factory.LazyAttribute(lambda obj: obj._generated_name.split(" ")[1])
19
+ email = factory.LazyAttribute(
20
+ lambda obj: f"{obj.first_name.lower()}.{obj.last_name.lower()}@example.com"
21
+ )
22
+
23
+
24
+ class TokenFactory(factory.django.DjangoModelFactory):
25
+ class Meta:
26
+ model = Token
27
+
28
+ access_token = factory.faker.Faker("pystr", min_chars=2_507, max_chars=2_507)
29
+ refresh_token = factory.faker.Faker("pystr", min_chars=24, max_chars=24)
30
+ user = factory.SubFactory(UserFactory)
31
+ character_id = factory.fuzzy.FuzzyInteger(low=90_000_000, high=98_000_000)
32
+ character_name = factory.faker.Faker("name")
33
+ token_type = "Character"
34
+ character_owner_hash = factory.faker.Faker("pystr", min_chars=28, max_chars=28)
35
+ sso_version = 2
36
+
37
+ @factory.post_generation
38
+ def scopes(self, create, extracted, **kwargs):
39
+ if not create:
40
+ return
41
+ if extracted:
42
+ for scope in extracted:
43
+ self.scopes.add(scope)
44
+
45
+
46
+ class ScopeFactory(factory.django.DjangoModelFactory):
47
+ class Meta:
48
+ model = Scope
49
+
50
+ name = factory.faker.Faker("word")
51
+ help_text = factory.faker.Faker("sentence")
52
+
53
+
54
+ class CallbackRedirectFactory(factory.django.DjangoModelFactory):
55
+ class Meta:
56
+ model = CallbackRedirect
57
+
58
+ session_key = factory.faker.Faker("md5")
59
+ url = factory.faker.Faker("uri_path", deep=3)
60
+ state = factory.faker.Faker("sha1")