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.
- django_esi-8.1.0.dist-info/METADATA +93 -0
- django_esi-8.1.0.dist-info/RECORD +100 -0
- django_esi-8.1.0.dist-info/WHEEL +4 -0
- django_esi-8.1.0.dist-info/licenses/LICENSE +674 -0
- esi/__init__.py +7 -0
- esi/admin.py +42 -0
- esi/aiopenapi3/client.py +79 -0
- esi/aiopenapi3/plugins.py +224 -0
- esi/app_settings.py +112 -0
- esi/apps.py +11 -0
- esi/checks.py +56 -0
- esi/clients.py +657 -0
- esi/decorators.py +271 -0
- esi/errors.py +22 -0
- esi/exceptions.py +51 -0
- esi/helpers.py +63 -0
- 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 +58 -0
- esi/locale/en/LC_MESSAGES/django.mo +0 -0
- esi/locale/en/LC_MESSAGES/django.po +54 -0
- esi/locale/es/LC_MESSAGES/django.mo +0 -0
- esi/locale/es/LC_MESSAGES/django.po +59 -0
- esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
- esi/locale/fr_FR/LC_MESSAGES/django.po +59 -0
- esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
- esi/locale/it_IT/LC_MESSAGES/django.po +59 -0
- esi/locale/ja/LC_MESSAGES/django.mo +0 -0
- esi/locale/ja/LC_MESSAGES/django.po +58 -0
- esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
- esi/locale/ko_KR/LC_MESSAGES/django.po +58 -0
- 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 +61 -0
- 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 +58 -0
- esi/management/commands/__init__.py +0 -0
- esi/management/commands/esi_clear_spec_cache.py +21 -0
- esi/management/commands/generate_esi_stubs.py +661 -0
- esi/management/commands/migrate_to_ssov2.py +188 -0
- esi/managers.py +303 -0
- esi/managers.pyi +85 -0
- esi/migrations/0001_initial.py +55 -0
- esi/migrations/0002_scopes_20161208.py +56 -0
- esi/migrations/0003_hide_tokens_from_admin_site.py +23 -0
- esi/migrations/0004_remove_unique_access_token.py +18 -0
- esi/migrations/0005_remove_token_length_limit.py +23 -0
- esi/migrations/0006_remove_url_length_limit.py +18 -0
- esi/migrations/0007_fix_mysql_8_migration.py +18 -0
- esi/migrations/0008_nullable_refresh_token.py +18 -0
- esi/migrations/0009_set_old_tokens_to_sso_v1.py +18 -0
- esi/migrations/0010_set_new_tokens_to_sso_v2.py +18 -0
- esi/migrations/0011_add_token_indices.py +28 -0
- esi/migrations/0012_fix_token_type_choices.py +18 -0
- esi/migrations/0013_squashed_0012_fix_token_type_choices.py +57 -0
- esi/migrations/__init__.py +0 -0
- esi/models.py +349 -0
- esi/openapi_clients.py +1225 -0
- esi/rate_limiting.py +107 -0
- esi/signals.py +21 -0
- esi/static/esi/img/EVE_SSO_Login_Buttons_Large_Black.png +0 -0
- esi/static/esi/img/EVE_SSO_Login_Buttons_Large_White.png +0 -0
- esi/static/esi/img/EVE_SSO_Login_Buttons_Small_Black.png +0 -0
- esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png +0 -0
- esi/stubs.py +2 -0
- esi/stubs.pyi +6807 -0
- esi/tasks.py +78 -0
- esi/templates/esi/select_token.html +116 -0
- esi/templatetags/__init__.py +0 -0
- esi/templatetags/scope_tags.py +8 -0
- esi/tests/__init__.py +134 -0
- esi/tests/client_authed_pilot.py +63 -0
- esi/tests/client_public_pilot.py +53 -0
- esi/tests/factories.py +47 -0
- esi/tests/factories_2.py +60 -0
- esi/tests/jwt_factory.py +135 -0
- esi/tests/test_checks.py +48 -0
- esi/tests/test_clients.py +1019 -0
- esi/tests/test_decorators.py +578 -0
- esi/tests/test_management_command.py +307 -0
- esi/tests/test_managers.py +673 -0
- esi/tests/test_models.py +403 -0
- esi/tests/test_openapi.json +854 -0
- esi/tests/test_openapi.py +1017 -0
- esi/tests/test_swagger.json +489 -0
- esi/tests/test_swagger_full.json +51112 -0
- esi/tests/test_tasks.py +116 -0
- esi/tests/test_templatetags.py +22 -0
- esi/tests/test_views.py +331 -0
- esi/tests/threading_pilot.py +69 -0
- esi/urls.py +9 -0
- 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
|
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}"
|
esi/tests/factories_2.py
ADDED
|
@@ -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")
|