Compare commits
18 commits
3dd62a0173
...
7ad52c59a4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ad52c59a4 | ||
|
|
f711ee34a1 | ||
|
|
25948dfc09 | ||
| e6ea563d19 | |||
|
|
cc6a76581e | ||
| f4fe506aef | |||
| 55dadd67f4 | |||
| 926e48a5dd | |||
|
|
30f23c8d49 | ||
| 43b5ba9028 | |||
| 706de57427 | |||
| 8859fe4d20 | |||
|
|
1981122f28 | ||
|
|
28ae006b5d | ||
|
|
f400eb6ab4 | ||
|
|
280b8f1c2c | ||
|
|
1f44f80a79 | ||
|
|
c227fda538 |
46 changed files with 2993 additions and 90 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -6,4 +6,5 @@ delme*.py
|
||||||
.env
|
.env
|
||||||
/venv/
|
/venv/
|
||||||
oidc.key
|
oidc.key
|
||||||
|
/log/
|
||||||
/logs/*.log
|
/logs/*.log
|
||||||
|
|
|
||||||
12
Dockerfile
12
Dockerfile
|
|
@ -1,9 +1,9 @@
|
||||||
FROM debian:13
|
FROM base:debian13
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apt update
|
#RUN apt update
|
||||||
RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip -y
|
#RUN apt install gnupg2 netcat-traditional libssl-dev libcrypto++-dev lsb-release python3-pip -y
|
||||||
COPY requirements.txt /app/requirements.txt
|
#COPY requirements.txt /app/requirements.txt
|
||||||
RUN pip3 install --break-system-packages -r requirements.txt
|
#RUN pip3 install --break-system-packages -r requirements.txt
|
||||||
RUN pip3 install --break-system-packages setuptools
|
#RUN pip3 install --break-system-packages setuptools
|
||||||
#ENTRYPOINT ["./run.sh"]
|
#ENTRYPOINT ["./run.sh"]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
|
@ -16,6 +18,8 @@ from apps.core.serializers import ConfigSerializer
|
||||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# @method_decorator(login_required, name='dispatch')
|
# @method_decorator(login_required, name='dispatch')
|
||||||
class HomeView(TemplateView):
|
class HomeView(TemplateView):
|
||||||
template_name = 'core/home.html'
|
template_name = 'core/home.html'
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,93 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from oauth2_provider.contrib.rest_framework import TokenMatchesOASRequirements, OAuth2Authentication
|
from oauth2_provider.contrib.rest_framework import TokenMatchesOASRequirements, OAuth2Authentication
|
||||||
from rest_framework.permissions import (
|
from rest_framework.permissions import (
|
||||||
IsAuthenticated
|
IsAuthenticated, BasePermission
|
||||||
)
|
)
|
||||||
|
|
||||||
log = logging.getLogger("oauth2_provider")
|
logger = logging.getLogger("oauth2_provider")
|
||||||
|
|
||||||
|
|
||||||
class IsAuthenticatedOrTokenMatchesOASRequirements(TokenMatchesOASRequirements):
|
class IsAuthenticatedOrTokenMatchesOASRequirements(TokenMatchesOASRequirements):
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
|
logger.debug(f'try to authenticate {request} for {view} in IsAuthenticatedOrTokenMatchesOASRequirements')
|
||||||
is_authenticated = IsAuthenticated().has_permission(request, view)
|
is_authenticated = IsAuthenticated().has_permission(request, view)
|
||||||
|
logger.debug(f'is_authenticated: {is_authenticated}')
|
||||||
oauth2authenticated = False
|
oauth2authenticated = False
|
||||||
if is_authenticated:
|
if is_authenticated:
|
||||||
oauth2authenticated = isinstance(request.successful_authenticator, OAuth2Authentication)
|
oauth2authenticated = isinstance(request.successful_authenticator, OAuth2Authentication)
|
||||||
|
|
||||||
|
logger.debug(f'oauth2authenticated: {oauth2authenticated}')
|
||||||
|
|
||||||
token_has_scope = TokenMatchesOASRequirements()
|
token_has_scope = TokenMatchesOASRequirements()
|
||||||
return (is_authenticated and not oauth2authenticated) or token_has_scope.has_permission(request, view)
|
logger.debug(f'token_has_scope: {token_has_scope}')
|
||||||
|
|
||||||
|
result = (is_authenticated and not oauth2authenticated) or token_has_scope.has_permission(request, view)
|
||||||
|
logger.debug(f'authentication result: {result}')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class TokenMatchesViewSetActions(BasePermission):
|
||||||
|
"""
|
||||||
|
:attr:action_required_scopes: dict keyed by view set action name with value: iterable action scope lists
|
||||||
|
|
||||||
|
This fulfills the [Open API Specification (OAS; formerly Swagger)](https://www.openapis.org/)
|
||||||
|
list of alternative Security Requirements Objects for oauth2 or openIdConnect:
|
||||||
|
When a list of Security Requirement Objects is defined on the Open API object or Operation Object,
|
||||||
|
only one of Security Requirement Objects in the list needs to be satisfied to authorize the request.
|
||||||
|
[1](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securityRequirementObject)
|
||||||
|
|
||||||
|
For each method, a list of lists of allowed scopes is tried in order and the first to match succeeds.
|
||||||
|
|
||||||
|
@example
|
||||||
|
required_action_scopes = {
|
||||||
|
'list': [['read']],
|
||||||
|
'create': [['create1','scope2'], ['alt-scope3'], ['alt-scope4','alt-scope5']],
|
||||||
|
}
|
||||||
|
|
||||||
|
TODO: DRY: subclass TokenHasScope and iterate over values of required_scope?
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
token = request.auth
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if hasattr(token, "scope"): # OAuth 2
|
||||||
|
required_action_scopes = self.get_required_action_scopes(request, view)
|
||||||
|
|
||||||
|
# TODO: use action map instead to analyze method
|
||||||
|
action_map = view.action_map
|
||||||
|
a = view.action
|
||||||
|
|
||||||
|
if a in required_action_scopes:
|
||||||
|
logger.debug(
|
||||||
|
"Required scopes alternatives to access resource: {0}".format(
|
||||||
|
required_action_scopes[a]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for alt in required_action_scopes[a]:
|
||||||
|
if token.is_valid(alt):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
logger.warning("no scope action defined for action {0}".format(a))
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert False, (
|
||||||
|
"TokenMatchesViewSetActions requires the"
|
||||||
|
"`oauth2_provider.rest_framework.OAuth2Authentication` authentication "
|
||||||
|
"class to be used."
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_required_action_scopes(self, request, view):
|
||||||
|
try:
|
||||||
|
return getattr(view, "required_action_scopes")
|
||||||
|
except AttributeError:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"TokenMatchesViewSetActions requires the view to"
|
||||||
|
" define the required_action_scopes attribute"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,12 @@ from unittest.mock import patch
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from oauth2_provider.models import get_access_token_model, get_application_model
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APIClient, APITestCase
|
from rest_framework.test import APIClient, APITestCase
|
||||||
|
|
||||||
|
|
||||||
from apps.core.models import Config
|
|
||||||
from apps.gooyal_oauth2.models import Scope
|
from apps.gooyal_oauth2.models import Scope
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
|
||||||
AccessToken = get_access_token_model()
|
|
||||||
Application = get_application_model()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def mock_notifications_push_user_success(user_uuid, title, message, priority=5, extras=None):
|
def mock_notifications_push_user_success(user_uuid, title, message, priority=5, extras=None):
|
||||||
import uuid as sys_uuid
|
import uuid as sys_uuid
|
||||||
|
|
@ -31,6 +24,12 @@ def mock_notifications_push_user_success(user_uuid, title, message, priority=5,
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
from oauth2_provider.models import get_access_token_model, get_application_model
|
||||||
|
|
||||||
|
AccessToken = get_access_token_model()
|
||||||
|
Application = get_application_model()
|
||||||
|
|
||||||
|
|
||||||
class GooyalOAuth2Tests(APITestCase):
|
class GooyalOAuth2Tests(APITestCase):
|
||||||
user_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb47005f')
|
user_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb47005f')
|
||||||
access_token_1 = 'au4naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
access_token_1 = 'au4naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
||||||
|
|
@ -46,6 +45,7 @@ class GooyalOAuth2Tests(APITestCase):
|
||||||
client = APIClient()
|
client = APIClient()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
settings.SMS_SEND = False
|
settings.SMS_SEND = False
|
||||||
|
|
||||||
|
|
@ -203,10 +203,3 @@ class GooyalOAuth2Tests(APITestCase):
|
||||||
result = self.client.post(reverse("gooyal_oauth2:revoke-token"), data=revoke_data)
|
result = self.client.post(reverse("gooyal_oauth2:revoke-token"), data=revoke_data)
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
self.assertEqual(AccessToken.objects.count(), 0)
|
self.assertEqual(AccessToken.objects.count(), 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import django_filters
|
import django_filters
|
||||||
from .models import User
|
from .models import User
|
||||||
|
|
||||||
|
class ListUUIDFilter(django_filters.BaseInFilter, django_filters.UUIDFilter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserFilter(django_filters.FilterSet):
|
class UserFilter(django_filters.FilterSet):
|
||||||
|
uuid_in = ListUUIDFilter(field_name='uuid', required=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = {
|
fields = {
|
||||||
'uuid': ['in'],
|
# 'uuid': ['in'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
60
apps/users/management/commands/send_sms_by_uuid_list.py
Normal file
60
apps/users/management/commands/send_sms_by_uuid_list.py
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
from apps.users.models import *
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "send sms by uuid list"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('message_file', type=str, help='Message as a file')
|
||||||
|
parser.add_argument('uuids_file', type=str, help='File with one UUID per line')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
print(options['message_file'])
|
||||||
|
print(options['uuids_file'])
|
||||||
|
message_file_path = options['message_file']
|
||||||
|
uuids_file_path = options['uuids_file']
|
||||||
|
|
||||||
|
if not os.path.exists(message_file_path):
|
||||||
|
raise CommandError(f'File "{message_file_path}" does not exist')
|
||||||
|
|
||||||
|
if not os.path.exists(uuids_file_path):
|
||||||
|
raise CommandError(f'File "{uuids_file_path}" does not exist')
|
||||||
|
|
||||||
|
uuids = []
|
||||||
|
invalid_lines = []
|
||||||
|
message: str = ''''''
|
||||||
|
|
||||||
|
with open(message_file_path, 'r', encoding='utf-8') as f:
|
||||||
|
message = f.read()
|
||||||
|
|
||||||
|
with open(uuids_file_path, 'r', encoding='utf-8') as f:
|
||||||
|
for line_num, line in enumerate(f, 1):
|
||||||
|
line = line.strip()
|
||||||
|
if not line: # Skip empty lines
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
# Validate and normalize UUID
|
||||||
|
valid_uuid = str(uuid.UUID(line))
|
||||||
|
uuids.append(valid_uuid)
|
||||||
|
self.stdout.write(f'Line {line_num}: ✓ {valid_uuid}')
|
||||||
|
except (ValueError, AttributeError, TypeError):
|
||||||
|
invalid_lines.append((line_num, line))
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(f'Line {line_num}: ✗ Invalid UUID "{line}"')
|
||||||
|
)
|
||||||
|
|
||||||
|
sms_client = SMSPolicy.get_client()
|
||||||
|
users = User.objects.filter(uuid__in=uuids)
|
||||||
|
empty_phone_numbers = list(filter(lambda u: not u.phone_number, users))
|
||||||
|
if empty_phone_numbers:
|
||||||
|
logger.error(f'not exist phone numbers for following uuids')
|
||||||
|
print(empty_phone_numbers)
|
||||||
|
logger.error(f'end of not exist phone numbers')
|
||||||
|
return
|
||||||
|
for user in users:
|
||||||
|
logger.info(f'try to send message for: {user.uuid} and {user.phone_number}')
|
||||||
|
sms_client.send_sms(user.phone_number.strip('+'), message)
|
||||||
|
logger.info(f'sent message for: {user.uuid} and {user.phone_number}')
|
||||||
|
|
@ -160,9 +160,6 @@ class User(AbstractUser):
|
||||||
return state.get_province_by_id(self.province)
|
return state.get_province_by_id(self.province)
|
||||||
|
|
||||||
def set_otp(self):
|
def set_otp(self):
|
||||||
if self.otp_expire and (self.otp_expire + timedelta(seconds=MAX_OTP_VALID_DURATION)) > timezone.now():
|
|
||||||
raise Exception(_('otp expire time not reached.'))
|
|
||||||
|
|
||||||
if not settings.SMS_SEND:
|
if not settings.SMS_SEND:
|
||||||
self._otp = '77501'
|
self._otp = '77501'
|
||||||
elif self.phone_number in settings.TEST_PHONENUMBERS:
|
elif self.phone_number in settings.TEST_PHONENUMBERS:
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from rest_framework import serializers
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
from utils.exceptions import UnprocessableEntity
|
from utils.exceptions import UnprocessableEntity
|
||||||
|
|
||||||
|
|
@ -62,6 +63,10 @@ class RequestOTPSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
read_only_fields = ['otp_expire', 'ttl']
|
read_only_fields = ['otp_expire', 'ttl']
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
attrs['phone_number'] = unidecode(attrs['phone_number'])
|
||||||
|
return attrs
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
# TODO: move it to query set
|
# TODO: move it to query set
|
||||||
validated_data = self.validated_data
|
validated_data = self.validated_data
|
||||||
|
|
@ -71,16 +76,9 @@ class RequestOTPSerializer(serializers.ModelSerializer):
|
||||||
user = User.objects.create_user(**validated_data)
|
user = User.objects.create_user(**validated_data)
|
||||||
|
|
||||||
if not user.otp_is_valid():
|
if not user.otp_is_valid():
|
||||||
try:
|
user.set_otp()
|
||||||
user.set_otp()
|
|
||||||
except Exception as e:
|
|
||||||
raise UnprocessableEntity(_('otp expire time not reached'))
|
|
||||||
|
|
||||||
user.send_otp()
|
user.send_otp()
|
||||||
|
|
||||||
else:
|
|
||||||
raise UnprocessableEntity(_('otp expire time not reached yet'))
|
|
||||||
|
|
||||||
self.instance = user
|
self.instance = user
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
@ -156,3 +154,9 @@ class SessionSerializer(serializers.ModelSerializer):
|
||||||
model = AccessToken
|
model = AccessToken
|
||||||
fields = ('uuid', 'created', "detail", "is_current")
|
fields = ('uuid', 'created', "detail", "is_current")
|
||||||
read_only_fields = ['uuid', 'created', "detail", "is_current" ]
|
read_only_fields = ['uuid', 'created', "detail", "is_current" ]
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: better solution
|
||||||
|
# this is a proxy api for submit event for promotion
|
||||||
|
class ReferralSetSerializer(serializers.Serializer):
|
||||||
|
referral = serializers.UUIDField()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ Application = get_application_model()
|
||||||
|
|
||||||
|
|
||||||
class UserTests(APITestCase):
|
class UserTests(APITestCase):
|
||||||
user_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb47005f')
|
user_1_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb47005f')
|
||||||
|
user_2_uuid = uuid.UUID('b14e8b86-8f4a-44d9-b29d-badceb47005e')
|
||||||
access_token_1 = 'au4naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
access_token_1 = 'au4naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
||||||
access_token_2 = 'vu4naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
access_token_2 = 'vu4naVsdKCbKNOhnElPyXcrwSnqqFbm'
|
||||||
application_uuid = uuid.UUID('a14e8b86-8f4a-44d9-b29d-badceb47005f')
|
application_uuid = uuid.UUID('a14e8b86-8f4a-44d9-b29d-badceb47005f')
|
||||||
|
|
@ -37,11 +38,14 @@ class UserTests(APITestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
settings.SMS_SEND = False
|
settings.SMS_SEND = False
|
||||||
self.user_phone_number = '+989100000000'
|
self.user_1_phone_number = '+989100000000'
|
||||||
self.user = User.objects.create(pk=self.user_uuid, phone_number=self.user_phone_number)
|
self.user_2_phone_number = '+989100000001'
|
||||||
|
self.user_1 = User.objects.create(pk=self.user_1_uuid, phone_number=self.user_1_phone_number)
|
||||||
|
self.user_2 = User.objects.create(pk=self.user_2_uuid, phone_number=self.user_2_phone_number)
|
||||||
|
|
||||||
Scope.objects.create(name='accounts.status:get', description='accounts.status:get')
|
Scope.objects.create(name='accounts.status:get', description='accounts.status:get')
|
||||||
Scope.objects.create(name='accounts.account:retrieve', description='accounts.account:retrieve')
|
Scope.objects.create(name='accounts.account:retrieve', description='accounts.account:retrieve')
|
||||||
|
Scope.objects.create(name='accounts.users.user:submit_referral', description='accounts.users.user:submit_referral')
|
||||||
|
|
||||||
self.application = Application.objects.create(
|
self.application = Application.objects.create(
|
||||||
client_id=self.client_id,
|
client_id=self.client_id,
|
||||||
|
|
@ -49,9 +53,9 @@ class UserTests(APITestCase):
|
||||||
authorization_grant_type='password',
|
authorization_grant_type='password',
|
||||||
hash_client_secret=False,
|
hash_client_secret=False,
|
||||||
uuid=self.application_uuid,
|
uuid=self.application_uuid,
|
||||||
user_id=self.user_uuid,
|
user_id=self.user_1_uuid,
|
||||||
max_allowed_session=1,
|
max_allowed_session=1,
|
||||||
allowed_scope='accounts.status:get accounts.account:retrieve',
|
allowed_scope='accounts.status:get accounts.account:retrieve accounts.users.user:submit_referral',
|
||||||
)
|
)
|
||||||
|
|
||||||
# self.sys_date_patcher = patch('simata_safte.models.get_sys_date', mock_get_sys_date)
|
# self.sys_date_patcher = patch('simata_safte.models.get_sys_date', mock_get_sys_date)
|
||||||
|
|
@ -70,7 +74,7 @@ class UserTests(APITestCase):
|
||||||
access_token_1 = AccessToken.objects.create(
|
access_token_1 = AccessToken.objects.create(
|
||||||
**{
|
**{
|
||||||
"token": self.access_token_1,
|
"token": self.access_token_1,
|
||||||
"user": self.user,
|
"user": self.user_1,
|
||||||
# "client_id": self.client_id,
|
# "client_id": self.client_id,
|
||||||
# "client_owner": owner,
|
# "client_owner": owner,
|
||||||
"application_id": self.application_uuid,
|
"application_id": self.application_uuid,
|
||||||
|
|
@ -88,7 +92,7 @@ class UserTests(APITestCase):
|
||||||
access_token_2 = AccessToken.objects.create(
|
access_token_2 = AccessToken.objects.create(
|
||||||
**{
|
**{
|
||||||
"token": self.access_token_2,
|
"token": self.access_token_2,
|
||||||
"user": self.user,
|
"user": self.user_1,
|
||||||
|
|
||||||
# "client_id": self.client_id,
|
# "client_id": self.client_id,
|
||||||
# "client_owner": owner,
|
# "client_owner": owner,
|
||||||
|
|
@ -122,13 +126,13 @@ class UserTests(APITestCase):
|
||||||
return basic_credentials
|
return basic_credentials
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
self.user.set_otp()
|
self.user_1.set_otp()
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"grant_type": "password",
|
"grant_type": "password",
|
||||||
"username": self.user_phone_number,
|
"username": self.user_1_phone_number,
|
||||||
"password": '77501',
|
"password": '77501',
|
||||||
"scope": 'accounts.status:get accounts.account:retrieve',
|
"scope": 'accounts.status:get accounts.account:retrieve accounts.users.user:submit_referral',
|
||||||
"auth_fields": 'phone_number:otp'
|
"auth_fields": 'phone_number:otp'
|
||||||
}
|
}
|
||||||
self.client.credentials(
|
self.client.credentials(
|
||||||
|
|
@ -136,6 +140,7 @@ class UserTests(APITestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
result = self.client.post(reverse("gooyal_oauth2:token"), data=data)
|
result = self.client.post(reverse("gooyal_oauth2:token"), data=data)
|
||||||
|
print(result.json())
|
||||||
access_token = result.json()['access_token']
|
access_token = result.json()['access_token']
|
||||||
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + access_token)
|
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + access_token)
|
||||||
|
|
||||||
|
|
@ -148,6 +153,17 @@ class UserTests(APITestCase):
|
||||||
response = self.client.get(reverse("users:user_sessions_api"))
|
response = self.client.get(reverse("users:user_sessions_api"))
|
||||||
self.assertEqual(response.json()['results'][0]['is_current'] , True)
|
self.assertEqual(response.json()['results'][0]['is_current'] , True)
|
||||||
|
|
||||||
|
def test_referralPromotion_allOK_success(self):
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
submit_referral_url = reverse("users:user-submit-referral")
|
||||||
|
submit_referral_data = {
|
||||||
|
"referral": self.user_2_uuid
|
||||||
|
}
|
||||||
|
response = self.client.post(submit_referral_url, submit_referral_data)
|
||||||
|
print(response.json())
|
||||||
|
# self.assertEqual(response.json()['results'][0]['is_current'] , True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.contrib.auth.views import LogoutView
|
from django.contrib.auth.views import LogoutView
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from .views import UserListView, UserPublicRetrieveView, AccountView, RequestOTPView, ChangePasswordView, \
|
from .views import UserListView, UserPublicRetrieveView, AccountView, RequestOTPView, ChangePasswordView, \
|
||||||
OTPLoginView, ProfileDetailView, ProfileUpdateView, RequestOTTView, UserCurrentAvatarUrlView, UserInquiryView, \
|
OTPLoginView, ProfileDetailView, ProfileUpdateView, RequestOTTView, UserCurrentAvatarUrlView, UserInquiryView, \
|
||||||
UserDetailedRetrieveView, UserSessionListView
|
UserDetailedRetrieveView, UserSessionListView, UserProfileView, UserViewSet
|
||||||
|
|
||||||
app_name = "users"
|
app_name = "users"
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register("user", UserViewSet, basename="user")
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('login/', OTPLoginView.as_view(), name='login'),
|
path('login/', OTPLoginView.as_view(), name='login'),
|
||||||
path('logout/', LogoutView.as_view(), name='logout'),
|
path('logout/', LogoutView.as_view(), name='logout'),
|
||||||
path('account/', ProfileDetailView.as_view(), name='account_detail'),
|
path('account/', ProfileDetailView.as_view(), name='account_detail'),
|
||||||
path('account/update/', ProfileUpdateView.as_view(), name='account_update'),
|
path('account/update/', ProfileUpdateView.as_view(), name='account_update'),
|
||||||
path('api/account/', AccountView.as_view(), name='account_api'),
|
path('api/account/', AccountView.as_view(), name='account_api'),
|
||||||
path('api/users/', UserListView.as_view(), name='user_list_api'),
|
path('api/users/all', UserListView.as_view(), name='user_list_api'),
|
||||||
|
path('api/users/', UserProfileView.as_view(), name='user_profile_list_api'),
|
||||||
path('api/sessions/', UserSessionListView.as_view(), name='user_sessions_api'),
|
path('api/sessions/', UserSessionListView.as_view(), name='user_sessions_api'),
|
||||||
path('api/users/<uuid>/', UserPublicRetrieveView.as_view(), name='user_public_retrieve_api'),
|
path('api/users/<uuid>/', UserPublicRetrieveView.as_view(), name='user_public_retrieve_api'),
|
||||||
path('api/users/<uuid>/avatar', UserCurrentAvatarUrlView.as_view(), name='user_avatar_api'),
|
path('api/users/<uuid>/avatar', UserCurrentAvatarUrlView.as_view(), name='user_avatar_api'),
|
||||||
|
|
@ -21,4 +27,5 @@ urlpatterns = [
|
||||||
path('api/request_ott/', RequestOTTView.as_view(), name='request_ott_api'),
|
path('api/request_ott/', RequestOTTView.as_view(), name='request_ott_api'),
|
||||||
path('api/change_password/', ChangePasswordView.as_view(), name='change_password_api'),
|
path('api/change_password/', ChangePasswordView.as_view(), name='change_password_api'),
|
||||||
path('api/inquiry/', UserInquiryView.as_view(), name='user_inquiry_api'),
|
path('api/inquiry/', UserInquiryView.as_view(), name='user_inquiry_api'),
|
||||||
]
|
] + router.urls
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.views import LoginView
|
from django.contrib.auth.views import LoginView
|
||||||
|
|
@ -9,34 +10,51 @@ from django.utils.decorators import method_decorator
|
||||||
from django.views.generic import DetailView, UpdateView
|
from django.views.generic import DetailView, UpdateView
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope
|
from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope
|
||||||
from rest_framework import generics, status, permissions
|
from rest_framework import generics, status, permissions, mixins
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.generics import get_object_or_404
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
|
||||||
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements
|
from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements, TokenMatchesViewSetActions
|
||||||
|
from apps.gooyal_oauth2.throttling import get_application
|
||||||
from apps.users.filters import UserFilter
|
from apps.users.filters import UserFilter
|
||||||
from apps.users.forms import OTPAuthenticationForm, ProfileUpdateForm
|
from apps.users.forms import OTPAuthenticationForm, ProfileUpdateForm
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from apps.users.provinces_and_cities import State
|
from apps.users.provinces_and_cities import State
|
||||||
from apps.users.serializers import PublicUserSerializer, AccountSerializer, RequestOTPSerializer, RequestOTTSerializer, \
|
from apps.users.serializers import PublicUserSerializer, AccountSerializer, RequestOTPSerializer, RequestOTTSerializer, \
|
||||||
ChangePasswordSerializer, UserInquirySerializer, SessionSerializer
|
ChangePasswordSerializer, UserInquirySerializer, SessionSerializer, ReferralSetSerializer
|
||||||
|
from utils.throttles import RequestOTPDayRateThrottle, RequestOTPMinRateThrottle, NumberedRequestOTPDayRateThrottle, NumberedRequestOTPMinRateThrottle
|
||||||
|
from utils.clients.promotions_client import promote_user
|
||||||
from utils.throttles import RequestOTPDayRateThrottle, RequestOTPMinRateThrottle
|
from utils.throttles import RequestOTPDayRateThrottle, RequestOTPMinRateThrottle
|
||||||
|
from utils import exceptions
|
||||||
|
|
||||||
UserModel = get_user_model()
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class UserListView(generics.ListAPIView):
|
class BaseUserListView(generics.ListAPIView):
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = PublicUserSerializer
|
serializer_class = PublicUserSerializer
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_class = UserFilter
|
|
||||||
|
|
||||||
|
# Make filterset_class abstract - must be defined in subclasses
|
||||||
|
filterset_class = None
|
||||||
|
|
||||||
|
class UserProfileView(BaseUserListView):
|
||||||
|
filterset_class = UserFilter
|
||||||
required_alternate_scopes = {
|
required_alternate_scopes = {
|
||||||
"GET": [["accounts.profile:list"]],
|
"GET": [["accounts.profile:list"]],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UserListView(BaseUserListView):
|
||||||
|
# filterset_class = UserFilter
|
||||||
|
required_alternate_scopes = {
|
||||||
|
"GET": [["accounts.users:list"]],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class UserPublicRetrieveView(generics.RetrieveAPIView):
|
class UserPublicRetrieveView(generics.RetrieveAPIView):
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
|
|
@ -47,6 +65,7 @@ class UserPublicRetrieveView(generics.RetrieveAPIView):
|
||||||
"GET": [["accounts.profile:retrieve"]],
|
"GET": [["accounts.profile:retrieve"]],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class UserDetailedRetrieveView(generics.RetrieveAPIView):
|
class UserDetailedRetrieveView(generics.RetrieveAPIView):
|
||||||
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements]
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
|
|
@ -56,6 +75,7 @@ class UserDetailedRetrieveView(generics.RetrieveAPIView):
|
||||||
"GET": [["accounts.profile:detailed_retrieve"]],
|
"GET": [["accounts.profile:detailed_retrieve"]],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class UserCurrentAvatarUrlView(generics.RetrieveAPIView):
|
class UserCurrentAvatarUrlView(generics.RetrieveAPIView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
|
|
@ -90,7 +110,11 @@ class RequestOTPView(generics.CreateAPIView):
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
serializer_class = RequestOTPSerializer
|
serializer_class = RequestOTPSerializer
|
||||||
required_scopes = []
|
required_scopes = []
|
||||||
throttle_classes = [RequestOTPMinRateThrottle, RequestOTPDayRateThrottle]
|
throttle_classes = [RequestOTPMinRateThrottle,
|
||||||
|
RequestOTPDayRateThrottle,
|
||||||
|
NumberedRequestOTPDayRateThrottle,
|
||||||
|
NumberedRequestOTPMinRateThrottle]
|
||||||
|
|
||||||
|
|
||||||
class RequestOTTView(generics.CreateAPIView):
|
class RequestOTTView(generics.CreateAPIView):
|
||||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
|
|
@ -197,3 +221,58 @@ class UserSessionListView(generics.ListAPIView):
|
||||||
from apps.gooyal_oauth2.models import AccessToken
|
from apps.gooyal_oauth2.models import AccessToken
|
||||||
return AccessToken.objects.filter(user=self.request.user).all()
|
return AccessToken.objects.filter(user=self.request.user).all()
|
||||||
|
|
||||||
|
|
||||||
|
class UserViewSet(
|
||||||
|
# mixins.CreateModelMixin,
|
||||||
|
GenericViewSet
|
||||||
|
):
|
||||||
|
serializer_class = AccountSerializer
|
||||||
|
|
||||||
|
permission_classes = [TokenMatchesViewSetActions]
|
||||||
|
required_action_scopes = {
|
||||||
|
"submit_referral": [["accounts.users.user:submit_referral"]],
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: better solution
|
||||||
|
# this is a proxy api for submit event in promotion service
|
||||||
|
@action(detail=False, methods=['POST'], serializer_class=ReferralSetSerializer)
|
||||||
|
def submit_referral(self, request, *args, **kwargs):
|
||||||
|
user = self.request.user
|
||||||
|
application = get_application(self.request)
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
referral = serializer.validated_data.get("referral")
|
||||||
|
|
||||||
|
if referral == self.request.user.pk:
|
||||||
|
raise exceptions.Conflict('SAME AS USER')
|
||||||
|
|
||||||
|
referral_user = get_object_or_404(User.objects.all(), pk=referral)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"referral": referral,
|
||||||
|
"application": application.pk,
|
||||||
|
"user": user.pk,
|
||||||
|
}
|
||||||
|
plan = settings.PROMOTIONS_REFERRAL_PLAN
|
||||||
|
|
||||||
|
result = promote_user(user, plan, 'accounts::submit_referral', data)
|
||||||
|
# result_json = {
|
||||||
|
# "promotions": [
|
||||||
|
# {
|
||||||
|
# "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
# "event": {
|
||||||
|
# "label": "string",
|
||||||
|
# "data": "string",
|
||||||
|
# "user": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
|
||||||
|
# "application": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||||
|
# },
|
||||||
|
# "promotion_amount": 2147483647,
|
||||||
|
# "state": 1,
|
||||||
|
# "user": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
# }
|
||||||
|
if not result:
|
||||||
|
raise exceptions.Conflict('can not set referral')
|
||||||
|
|
||||||
|
return Response(serializer.validated_data)
|
||||||
|
|
|
||||||
32
client.py
32
client.py
|
|
@ -3,10 +3,10 @@ import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
OAUTH_CLIENT_ID = 'xrFXKf53jrxVOygbLEoqrtOlUwBP00jIQ4zVpzc6'
|
OAUTH_CLIENT_ID = '4INGOCMoulE0fNY1SQlTbPtsWqqxGj2DdqjADq6u'
|
||||||
OAUTH_CLIENT_SECRET = 'qelUdRCdEEalVdko5aHRojxsC3CaL29yjwxmc6FeWs39SeVSb6aM9mNrYQixMJNTYQK7s2wffwPW94JPPbP6jTdd9dSf1mlqYcr6hW2COuayFUk4jP33OWu4taUYP2F5'
|
OAUTH_CLIENT_SECRET = 'KPc4dMSztNwAIB3vii3geXrzC1mKUIsAztz3t2ylC3HlrgJudJWhdrtoY6XeRJIuadTAREYYsXk9XtFHUDfPVcJvyfHMpNkz2VvghwrijhVprok0VYV7XrOirJ5nFUxD'
|
||||||
|
|
||||||
API_URI = 'https://accounts.gooyal.com'
|
API_URI = 'https://accounts.gooyal.ir'
|
||||||
|
|
||||||
|
|
||||||
# API_URI = 'http://127.0.0.1:8000'
|
# API_URI = 'http://127.0.0.1:8000'
|
||||||
|
|
@ -27,7 +27,7 @@ class ApiClient():
|
||||||
"grant_type": "password",
|
"grant_type": "password",
|
||||||
"username": phone_number,
|
"username": phone_number,
|
||||||
"password": password,
|
"password": password,
|
||||||
"scope": 'accounts.account:request_ott accounts.account:change_password accounts.profile:inquiry accounts.account:update accounts.account:retrieve accounts.profile:retrieve accounts.profile:list introspection wallet.wallet:get_balance',
|
"scope": 'introspection',
|
||||||
"auth_fields": 'phone_number:otp'
|
"auth_fields": 'phone_number:otp'
|
||||||
}
|
}
|
||||||
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
||||||
|
|
@ -51,7 +51,8 @@ class ApiClient():
|
||||||
"grant_type": "password",
|
"grant_type": "password",
|
||||||
"username": phone_number,
|
"username": phone_number,
|
||||||
"password": token,
|
"password": token,
|
||||||
"scope": 'introspection education.course:retrieve education.course:submit superapp.applications:list accounts.account:request_ott accounts.profile:list accounts.profile:retrieve accounts.account:retrieve accounts.account:update accounts.profile:inquiry accounts.account:change_password wallet.transaction:list wallet.invoice:create wallet.transaction:retrieve wallet.invoice:pay wallet.invoice:receipt wallet.deposit:submit wallet.deposit:verify wallet.withdraw:submit wallet.withdraw:verify ',
|
# "scope": 'introspection education.course:retrieve education.course:submit superapp.applications:list accounts.account:request_ott accounts.profile:list accounts.profile:retrieve accounts.account:retrieve accounts.account:update accounts.profile:inquiry accounts.account:change_password wallet.transaction:list wallet.invoice:create wallet.transaction:retrieve wallet.invoice:pay wallet.invoice:receipt wallet.deposit:submit wallet.deposit:verify wallet.withdraw:submit wallet.withdraw:verify ',
|
||||||
|
"scope": 'introspection',
|
||||||
"auth_fields": 'phone_number:ott'
|
"auth_fields": 'phone_number:ott'
|
||||||
}
|
}
|
||||||
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
||||||
|
|
@ -156,6 +157,7 @@ class ApiClient():
|
||||||
}
|
}
|
||||||
url = f'{API_URI}/{path}'
|
url = f'{API_URI}/{path}'
|
||||||
response = self._request(url, method='PUT', data=data)
|
response = self._request(url, method='PUT', data=data)
|
||||||
|
print(response)
|
||||||
return response and 'code' in response, response
|
return response and 'code' in response, response
|
||||||
|
|
||||||
def introspect_account(self, token):
|
def introspect_account(self, token):
|
||||||
|
|
@ -185,19 +187,29 @@ class ApiClient():
|
||||||
|
|
||||||
return self._request(url=url)
|
return self._request(url=url)
|
||||||
|
|
||||||
def get_wallet_balance(self):
|
def get_application_preferences(self):
|
||||||
url = 'http://127.0.0.1:8000/wallet/api/wallet/default/balance'
|
url = 'https://preferences.gooyal.com/data/application/'
|
||||||
|
|
||||||
return self._request(url=url)
|
return self._request(url=url)
|
||||||
|
|
||||||
|
|
||||||
|
auth_data = {'access_token': 'DJ1mycH9r5FCwaNgDA9nB9uU3KWt9m', 'expires_in': 36000, 'token_type': 'Bearer', 'scope': 'accounts.account:change_password accounts.account:update accounts.account:retrieve wallet.wallet:get_balance billboard_merchant.billboard:retrieve billboard_merchant.billboard:update billboard_merchant.billboard:create wallet.user:transaction_list data_crud.data:retrieve data_crud.data:update data_crud.data:delete', 'refresh_token': 'VF4P11tfcnGv9tc2u0qH6035OW5qU4'}
|
||||||
|
|
||||||
client = ApiClient('+989106853582')
|
client = ApiClient('+989106853582')
|
||||||
|
# client.auth_data = auth_data
|
||||||
# print(client.login_as_client_credentials())
|
# print(client.login_as_client_credentials())
|
||||||
print(client.request_otp())
|
print(client.request_otp())
|
||||||
print(client.otp_login('12345'))
|
import time
|
||||||
print(client.introspect_account('V3LUQ9OryHOy6q5iWwLBRAtRBm80ex'))
|
start = time.time()
|
||||||
print(client.get_wallet_balance())
|
print(client.otp_login('77502'))
|
||||||
|
# end = time.time()
|
||||||
|
# print(end - start)
|
||||||
|
# print(client.get_application_preferences())
|
||||||
|
# exit()
|
||||||
|
# print(client.introspect_account('V3LUQ9OryHOy6q5iWwLBRAtRBm80ex'))
|
||||||
|
# print(client.get_wallet_balance())
|
||||||
# ott = client.get_application_token()[1]['ott']
|
# ott = client.get_application_token()[1]['ott']
|
||||||
# print(client.ott_login(ott))
|
# print(client.ott_login(ott))
|
||||||
# print(ott)
|
# print(ott)
|
||||||
|
client.update_account('test')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
# from django.conf import settings
|
# from django.conf import settings
|
||||||
#
|
#
|
||||||
# # set the default Django settings module for the 'celery' program.
|
# # set the default Django settings module for the 'celery' program.
|
||||||
# # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'accounts.settings')
|
# # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'main.settings')
|
||||||
#
|
#
|
||||||
# app = Celery('notification_service')
|
# app = Celery('notification_service')
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,9 @@ REST_FRAMEWORK = {
|
||||||
],
|
],
|
||||||
'DEFAULT_THROTTLE_RATES': {
|
'DEFAULT_THROTTLE_RATES': {
|
||||||
'otp_min': '2/min',
|
'otp_min': '2/min',
|
||||||
'otp_day': '200/day',
|
'otp_day': '50/day',
|
||||||
|
'numbered_otp_min': '1/min',
|
||||||
|
'numbered_otp_day': '10/day',
|
||||||
},
|
},
|
||||||
'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler',
|
'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler',
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +295,7 @@ CACHES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOKI_BASE_PUBLIC_URL = config('LOKI_BASE_PUBLIC_URL', default=None, cast=str)
|
||||||
|
|
||||||
from main.other_settings.logging import LOGGING
|
from main.other_settings.logging import LOGGING
|
||||||
|
|
||||||
|
|
@ -304,10 +306,10 @@ STORAGES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django_minio_backend.models.MinioBackend",
|
"BACKEND": "django_minio_backend.models.MinioBackend",
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"MINIO_ENDPOINT": config('MINIO_ENDPOINT', default='drive.gooyal.com'),
|
"MINIO_ENDPOINT": config('MINIO_ENDPOINT', default='drive.gooyal.ir'),
|
||||||
"MINIO_USE_HTTPS": config('MINIO_USE_HTTPS', default=True, cast=bool),
|
"MINIO_USE_HTTPS": config('MINIO_USE_HTTPS', default=True, cast=bool),
|
||||||
|
|
||||||
"MINIO_EXTERNAL_ENDPOINT": config('MINIO_EXTERNAL_ENDPOINT', default='drive.gooyal.com'),
|
"MINIO_EXTERNAL_ENDPOINT": config('MINIO_EXTERNAL_ENDPOINT', default='drive.gooyal.ir'),
|
||||||
# Default is same as MINIO_ENDPOINT
|
# Default is same as MINIO_ENDPOINT
|
||||||
"MINIO_EXTERNAL_ENDPOINT_USE_HTTPS": config('MINIO_EXTERNAL_ENDPOINT_USE_HTTPS', default=True, cast=bool),
|
"MINIO_EXTERNAL_ENDPOINT_USE_HTTPS": config('MINIO_EXTERNAL_ENDPOINT_USE_HTTPS', default=True, cast=bool),
|
||||||
# Default is same as MINIO_USE_HTTPS
|
# Default is same as MINIO_USE_HTTPS
|
||||||
|
|
@ -315,7 +317,7 @@ STORAGES = {
|
||||||
"MINIO_ACCESS_KEY": config('MINIO_ACCESS_KEY'),
|
"MINIO_ACCESS_KEY": config('MINIO_ACCESS_KEY'),
|
||||||
"MINIO_SECRET_KEY": config('MINIO_SECRET_KEY'),
|
"MINIO_SECRET_KEY": config('MINIO_SECRET_KEY'),
|
||||||
"MINIO_URL_EXPIRY_HOURS": timedelta(days=1), # Default is 7 days (longest) if not defined
|
"MINIO_URL_EXPIRY_HOURS": timedelta(days=1), # Default is 7 days (longest) if not defined
|
||||||
"MINIO_CONSISTENCY_CHECK_ON_START": False,
|
"MINIO_CONSISTENCY_CHECK_ON_START": True,
|
||||||
"MINIO_DEFAULT_BUCKET": config('MINIO_MEDIA_FILES_BUCKET'), # replacement for MEDIA_ROOT
|
"MINIO_DEFAULT_BUCKET": config('MINIO_MEDIA_FILES_BUCKET'), # replacement for MEDIA_ROOT
|
||||||
# MINIO_STATIC_FILES_BUCKET = 'my-static-files-bucket' # replacement for STATIC_ROOT
|
# MINIO_STATIC_FILES_BUCKET = 'my-static-files-bucket' # replacement for STATIC_ROOT
|
||||||
|
|
||||||
|
|
@ -360,7 +362,7 @@ MINIO_ENDPOINT = config('MINIO_ENDPOINT', default='drive.gooyal.ir')
|
||||||
MINIO_USE_HTTPS = config('MINIO_USE_HTTPS', default=True, cast=bool)
|
MINIO_USE_HTTPS = config('MINIO_USE_HTTPS', default=True, cast=bool)
|
||||||
|
|
||||||
MINIO_EXTERNAL_ENDPOINT = config('MINIO_EXTERNAL_ENDPOINT',
|
MINIO_EXTERNAL_ENDPOINT = config('MINIO_EXTERNAL_ENDPOINT',
|
||||||
default='drive.gooyal.com') # Default is same as MINIO_ENDPOINT
|
default='drive.gooyal.ir') # Default is same as MINIO_ENDPOINT
|
||||||
MINIO_EXTERNAL_ENDPOINT_USE_HTTPS = config('MINIO_EXTERNAL_ENDPOINT_USE_HTTPS', default=True,
|
MINIO_EXTERNAL_ENDPOINT_USE_HTTPS = config('MINIO_EXTERNAL_ENDPOINT_USE_HTTPS', default=True,
|
||||||
cast=bool) # Default is same as MINIO_USE_HTTPS
|
cast=bool) # Default is same as MINIO_USE_HTTPS
|
||||||
MINIO_REGION = None # Default is set to None
|
MINIO_REGION = None # Default is set to None
|
||||||
|
|
@ -382,3 +384,7 @@ MINIO_POLICY_HOOKS: List[Tuple[str, dict]] = []
|
||||||
MINIO_BUCKET_CHECK_ON_SAVE = True # Default: True // Creates bucket if missing, then save
|
MINIO_BUCKET_CHECK_ON_SAVE = True # Default: True // Creates bucket if missing, then save
|
||||||
|
|
||||||
MOBIN_SMS_TOKEN = config('MOBIN_SMS_TOKEN', default='')
|
MOBIN_SMS_TOKEN = config('MOBIN_SMS_TOKEN', default='')
|
||||||
|
|
||||||
|
PROMOTIONS_BASE_PUBLIC_URL = config('PROMOTIONS_BASE_PUBLIC_URL', default=None, cast=str)
|
||||||
|
PROMOTIONS_BASE_PRIVATE_URL = config('PROMOTIONS_BASE_PRIVATE_URL', default=None, cast=str)
|
||||||
|
PROMOTIONS_REFERRAL_PLAN = config('PROMOTIONS_REFERRAL_PLAN', default=None, cast=str)
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,5 @@ django_redis
|
||||||
django_minio_backend
|
django_minio_backend
|
||||||
daphne
|
daphne
|
||||||
Twisted[tls,http2]
|
Twisted[tls,http2]
|
||||||
packaging==25.0
|
packaging==25.0
|
||||||
|
unidecode
|
||||||
|
|
@ -246,3 +246,4 @@ zope-interface==8.2
|
||||||
# via
|
# via
|
||||||
# gevent
|
# gevent
|
||||||
# twisted
|
# twisted
|
||||||
|
unidecode==1.4.0
|
||||||
|
|
|
||||||
4
run.sh
4
run.sh
|
|
@ -4,6 +4,6 @@
|
||||||
# sleep 3
|
# sleep 3
|
||||||
#done
|
#done
|
||||||
#python3 manage.py collectstatic --noinput
|
#python3 manage.py collectstatic --noinput
|
||||||
#python3 manage.py migrate
|
python3 manage.py migrate
|
||||||
gunicorn main.wsgi:application --bind 0.0.0.0:8000 -w 4
|
gunicorn main.wsgi:application --bind 0.0.0.0:8000 -w 4
|
||||||
#daphne -b 0.0.0.0 -p 8000 accounts.asgi:application
|
#daphne -b 0.0.0.0 -p 8000 main.asgi:application
|
||||||
8
utils/clients/gooyal_promotions_client/__init__.py
Normal file
8
utils/clients/gooyal_promotions_client/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
"""A client library for accessing service"""
|
||||||
|
|
||||||
|
from .client import AuthenticatedClient, Client
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"AuthenticatedClient",
|
||||||
|
"Client",
|
||||||
|
)
|
||||||
1
utils/clients/gooyal_promotions_client/api/__init__.py
Normal file
1
utils/clients/gooyal_promotions_client/api/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
"""Contains methods for accessing the API"""
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""Contains endpoint functions for accessing the API"""
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.event import Event
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
body: Event,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
headers: dict[str, Any] = {}
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "post",
|
||||||
|
"url": "/api/v2/promotions/application/{user_uuid}/event/".format(
|
||||||
|
user_uuid=quote(str(user_uuid), safe=""),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
_kwargs["json"] = body.to_dict()
|
||||||
|
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
_kwargs["headers"] = headers
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Event | None:
|
||||||
|
if response.status_code == 201:
|
||||||
|
response_201 = Event.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_201
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Event]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Event,
|
||||||
|
) -> Response[Event]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
body (Event):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Event]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Event,
|
||||||
|
) -> Event | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
body (Event):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Event
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Event,
|
||||||
|
) -> Response[Event]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
body (Event):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Event]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Event,
|
||||||
|
) -> Event | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
body (Event):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Event
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.paginated_promotion_list import PaginatedPromotionList
|
||||||
|
from ...types import UNSET, Response, Unset
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
|
params: dict[str, Any] = {}
|
||||||
|
|
||||||
|
params["limit"] = limit
|
||||||
|
|
||||||
|
params["offset"] = offset
|
||||||
|
|
||||||
|
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "get",
|
||||||
|
"url": "/api/v2/promotions/application/{user_uuid}/plan/".format(
|
||||||
|
user_uuid=quote(str(user_uuid), safe=""),
|
||||||
|
),
|
||||||
|
"params": params,
|
||||||
|
}
|
||||||
|
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> PaginatedPromotionList | None:
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_200 = PaginatedPromotionList.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_200
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(
|
||||||
|
*, client: AuthenticatedClient | Client, response: httpx.Response
|
||||||
|
) -> Response[PaginatedPromotionList]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> Response[PaginatedPromotionList]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
limit (int | Unset):
|
||||||
|
offset (int | Unset):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[PaginatedPromotionList]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> PaginatedPromotionList | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
limit (int | Unset):
|
||||||
|
offset (int | Unset):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PaginatedPromotionList
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
client=client,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> Response[PaginatedPromotionList]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
limit (int | Unset):
|
||||||
|
offset (int | Unset):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[PaginatedPromotionList]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
user_uuid: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> PaginatedPromotionList | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
limit (int | Unset):
|
||||||
|
offset (int | Unset):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PaginatedPromotionList
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
client=client,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.promote import Promote
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
body: Promote,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
headers: dict[str, Any] = {}
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "post",
|
||||||
|
"url": "/api/v2/promotions/application/{user_uuid}/plan/{id}/promote/".format(
|
||||||
|
user_uuid=quote(str(user_uuid), safe=""),
|
||||||
|
id=quote(str(id), safe=""),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
_kwargs["json"] = body.to_dict()
|
||||||
|
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
_kwargs["headers"] = headers
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Promote | None:
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_200 = Promote.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_200
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Promote]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Response[Promote]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
id (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promote]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
id=id,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Promote | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
id (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promote
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
id=id,
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Response[Promote]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
id (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promote]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
id=id,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Promote | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
id (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promote
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
id=id,
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.promotion import Promotion
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "get",
|
||||||
|
"url": "/api/v2/promotions/application/{user_uuid}/plan/{id}/".format(
|
||||||
|
user_uuid=quote(str(user_uuid), safe=""),
|
||||||
|
id=quote(str(id), safe=""),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Promotion | None:
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_200 = Promotion.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_200
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Promotion]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
) -> Response[Promotion]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
id (str):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promotion]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
id=id,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
) -> Promotion | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
id (str):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promotion
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
id=id,
|
||||||
|
client=client,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
) -> Response[Promotion]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
id (str):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promotion]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
id=id,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
user_uuid: str,
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
) -> Promotion | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
user_uuid (str):
|
||||||
|
id (str):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promotion
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
user_uuid=user_uuid,
|
||||||
|
id=id,
|
||||||
|
client=client,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""Contains endpoint functions for accessing the API"""
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.promote import Promote
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
*,
|
||||||
|
body: Promote,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
headers: dict[str, Any] = {}
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "post",
|
||||||
|
"url": "/promotions/api/v1/events/submit",
|
||||||
|
}
|
||||||
|
|
||||||
|
_kwargs["json"] = body.to_dict()
|
||||||
|
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
_kwargs["headers"] = headers
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Promote | None:
|
||||||
|
if response.status_code == 201:
|
||||||
|
response_201 = Promote.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_201
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Promote]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Response[Promote]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promote]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Promote | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promote
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Response[Promote]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promote]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Promote | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promote
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.promote import Promote
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
body: Promote,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
headers: dict[str, Any] = {}
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "post",
|
||||||
|
"url": "/promotions/api/v1/plans/{plan}/".format(
|
||||||
|
plan=quote(str(plan), safe=""),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
_kwargs["json"] = body.to_dict()
|
||||||
|
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
_kwargs["headers"] = headers
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Promote | None:
|
||||||
|
if response.status_code == 201:
|
||||||
|
response_201 = Promote.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_201
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Promote]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Response[Promote]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promote]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
plan=plan,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Promote | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promote
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
plan=plan,
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Response[Promote]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promote]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
plan=plan,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Promote | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promote
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
plan=plan,
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.paginated_promotion_list import PaginatedPromotionList
|
||||||
|
from ...types import UNSET, Response, Unset
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
|
params: dict[str, Any] = {}
|
||||||
|
|
||||||
|
params["limit"] = limit
|
||||||
|
|
||||||
|
params["offset"] = offset
|
||||||
|
|
||||||
|
params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "get",
|
||||||
|
"url": "/promotions/api/v1/plans/{plan}/promotins".format(
|
||||||
|
plan=quote(str(plan), safe=""),
|
||||||
|
),
|
||||||
|
"params": params,
|
||||||
|
}
|
||||||
|
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> PaginatedPromotionList | None:
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_200 = PaginatedPromotionList.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_200
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(
|
||||||
|
*, client: AuthenticatedClient | Client, response: httpx.Response
|
||||||
|
) -> Response[PaginatedPromotionList]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> Response[PaginatedPromotionList]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan (str):
|
||||||
|
limit (int | Unset):
|
||||||
|
offset (int | Unset):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[PaginatedPromotionList]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
plan=plan,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> PaginatedPromotionList | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan (str):
|
||||||
|
limit (int | Unset):
|
||||||
|
offset (int | Unset):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PaginatedPromotionList
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
plan=plan,
|
||||||
|
client=client,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> Response[PaginatedPromotionList]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan (str):
|
||||||
|
limit (int | Unset):
|
||||||
|
offset (int | Unset):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[PaginatedPromotionList]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
plan=plan,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
plan: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
limit: int | Unset = UNSET,
|
||||||
|
offset: int | Unset = UNSET,
|
||||||
|
) -> PaginatedPromotionList | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
plan (str):
|
||||||
|
limit (int | Unset):
|
||||||
|
offset (int | Unset):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PaginatedPromotionList
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
plan=plan,
|
||||||
|
client=client,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.promote import Promote
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
body: Promote,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
headers: dict[str, Any] = {}
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "post",
|
||||||
|
"url": "/promotions/api/v2/plans/{id}/promote/".format(
|
||||||
|
id=quote(str(id), safe=""),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
_kwargs["json"] = body.to_dict()
|
||||||
|
|
||||||
|
headers["Content-Type"] = "application/json"
|
||||||
|
|
||||||
|
_kwargs["headers"] = headers
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Promote | None:
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_200 = Promote.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_200
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[Promote]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Response[Promote]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
id (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promote]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
id=id,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Promote | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
id (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promote
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
id=id,
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Response[Promote]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
id (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Promote]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
id=id,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
body: Promote,
|
||||||
|
) -> Promote | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
id (str):
|
||||||
|
body (Promote):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Promote
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
id=id,
|
||||||
|
client=client,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.user_plan import UserPlan
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
id: str,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
|
_kwargs: dict[str, Any] = {
|
||||||
|
"method": "get",
|
||||||
|
"url": "/promotions/api/v2/plans/{id}/".format(
|
||||||
|
id=quote(str(id), safe=""),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return _kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> UserPlan | None:
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_200 = UserPlan.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_200
|
||||||
|
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[UserPlan]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
) -> Response[UserPlan]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
id (str):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[UserPlan]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
id=id,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
) -> UserPlan | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
id (str):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserPlan
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
id=id,
|
||||||
|
client=client,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
) -> Response[UserPlan]:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
id (str):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[UserPlan]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
id=id,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
id: str,
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
) -> UserPlan | None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
id (str):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserPlan
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
id=id,
|
||||||
|
client=client,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
268
utils/clients/gooyal_promotions_client/client.py
Normal file
268
utils/clients/gooyal_promotions_client/client.py
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
import ssl
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from attrs import define, evolve, field
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class Client:
|
||||||
|
"""A class for keeping track of data related to the API
|
||||||
|
|
||||||
|
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
|
||||||
|
|
||||||
|
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
|
||||||
|
|
||||||
|
``cookies``: A dictionary of cookies to be sent with every request
|
||||||
|
|
||||||
|
``headers``: A dictionary of headers to be sent with every request
|
||||||
|
|
||||||
|
``timeout``: The maximum amount of a time a request can take. API functions will raise
|
||||||
|
httpx.TimeoutException if this is exceeded.
|
||||||
|
|
||||||
|
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
|
||||||
|
but can be set to False for testing purposes.
|
||||||
|
|
||||||
|
``follow_redirects``: Whether or not to follow redirects. Default value is False.
|
||||||
|
|
||||||
|
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
|
||||||
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
|
||||||
|
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
|
||||||
|
argument to the constructor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
|
||||||
|
_base_url: str = field(alias="base_url")
|
||||||
|
_cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
|
||||||
|
_headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
|
||||||
|
_timeout: httpx.Timeout | None = field(default=None, kw_only=True, alias="timeout")
|
||||||
|
_verify_ssl: str | bool | ssl.SSLContext = field(default=True, kw_only=True, alias="verify_ssl")
|
||||||
|
_follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects")
|
||||||
|
_httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
|
||||||
|
_client: httpx.Client | None = field(default=None, init=False)
|
||||||
|
_async_client: httpx.AsyncClient | None = field(default=None, init=False)
|
||||||
|
|
||||||
|
def with_headers(self, headers: dict[str, str]) -> "Client":
|
||||||
|
"""Get a new client matching this one with additional headers"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.headers.update(headers)
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.headers.update(headers)
|
||||||
|
return evolve(self, headers={**self._headers, **headers})
|
||||||
|
|
||||||
|
def with_cookies(self, cookies: dict[str, str]) -> "Client":
|
||||||
|
"""Get a new client matching this one with additional cookies"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.cookies.update(cookies)
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.cookies.update(cookies)
|
||||||
|
return evolve(self, cookies={**self._cookies, **cookies})
|
||||||
|
|
||||||
|
def with_timeout(self, timeout: httpx.Timeout) -> "Client":
|
||||||
|
"""Get a new client matching this one with a new timeout configuration"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.timeout = timeout
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.timeout = timeout
|
||||||
|
return evolve(self, timeout=timeout)
|
||||||
|
|
||||||
|
def set_httpx_client(self, client: httpx.Client) -> "Client":
|
||||||
|
"""Manually set the underlying httpx.Client
|
||||||
|
|
||||||
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
||||||
|
"""
|
||||||
|
self._client = client
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_httpx_client(self) -> httpx.Client:
|
||||||
|
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
|
||||||
|
if self._client is None:
|
||||||
|
self._client = httpx.Client(
|
||||||
|
base_url=self._base_url,
|
||||||
|
cookies=self._cookies,
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=self._timeout,
|
||||||
|
verify=self._verify_ssl,
|
||||||
|
follow_redirects=self._follow_redirects,
|
||||||
|
**self._httpx_args,
|
||||||
|
)
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def __enter__(self) -> "Client":
|
||||||
|
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
|
||||||
|
self.get_httpx_client().__enter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
|
||||||
|
self.get_httpx_client().__exit__(*args, **kwargs)
|
||||||
|
|
||||||
|
def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
|
||||||
|
"""Manually set the underlying httpx.AsyncClient
|
||||||
|
|
||||||
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
||||||
|
"""
|
||||||
|
self._async_client = async_client
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_async_httpx_client(self) -> httpx.AsyncClient:
|
||||||
|
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
|
||||||
|
if self._async_client is None:
|
||||||
|
self._async_client = httpx.AsyncClient(
|
||||||
|
base_url=self._base_url,
|
||||||
|
cookies=self._cookies,
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=self._timeout,
|
||||||
|
verify=self._verify_ssl,
|
||||||
|
follow_redirects=self._follow_redirects,
|
||||||
|
**self._httpx_args,
|
||||||
|
)
|
||||||
|
return self._async_client
|
||||||
|
|
||||||
|
async def __aenter__(self) -> "Client":
|
||||||
|
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
|
||||||
|
await self.get_async_httpx_client().__aenter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
|
||||||
|
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class AuthenticatedClient:
|
||||||
|
"""A Client which has been authenticated for use on secured endpoints
|
||||||
|
|
||||||
|
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
|
||||||
|
|
||||||
|
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
|
||||||
|
|
||||||
|
``cookies``: A dictionary of cookies to be sent with every request
|
||||||
|
|
||||||
|
``headers``: A dictionary of headers to be sent with every request
|
||||||
|
|
||||||
|
``timeout``: The maximum amount of a time a request can take. API functions will raise
|
||||||
|
httpx.TimeoutException if this is exceeded.
|
||||||
|
|
||||||
|
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
|
||||||
|
but can be set to False for testing purposes.
|
||||||
|
|
||||||
|
``follow_redirects``: Whether or not to follow redirects. Default value is False.
|
||||||
|
|
||||||
|
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
|
||||||
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
|
||||||
|
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
|
||||||
|
argument to the constructor.
|
||||||
|
token: The token to use for authentication
|
||||||
|
prefix: The prefix to use for the Authorization header
|
||||||
|
auth_header_name: The name of the Authorization header
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
|
||||||
|
_base_url: str = field(alias="base_url")
|
||||||
|
_cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
|
||||||
|
_headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
|
||||||
|
_timeout: httpx.Timeout | None = field(default=None, kw_only=True, alias="timeout")
|
||||||
|
_verify_ssl: str | bool | ssl.SSLContext = field(default=True, kw_only=True, alias="verify_ssl")
|
||||||
|
_follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects")
|
||||||
|
_httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
|
||||||
|
_client: httpx.Client | None = field(default=None, init=False)
|
||||||
|
_async_client: httpx.AsyncClient | None = field(default=None, init=False)
|
||||||
|
|
||||||
|
token: str
|
||||||
|
prefix: str = "Bearer"
|
||||||
|
auth_header_name: str = "Authorization"
|
||||||
|
|
||||||
|
def with_headers(self, headers: dict[str, str]) -> "AuthenticatedClient":
|
||||||
|
"""Get a new client matching this one with additional headers"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.headers.update(headers)
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.headers.update(headers)
|
||||||
|
return evolve(self, headers={**self._headers, **headers})
|
||||||
|
|
||||||
|
def with_cookies(self, cookies: dict[str, str]) -> "AuthenticatedClient":
|
||||||
|
"""Get a new client matching this one with additional cookies"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.cookies.update(cookies)
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.cookies.update(cookies)
|
||||||
|
return evolve(self, cookies={**self._cookies, **cookies})
|
||||||
|
|
||||||
|
def with_timeout(self, timeout: httpx.Timeout) -> "AuthenticatedClient":
|
||||||
|
"""Get a new client matching this one with a new timeout configuration"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.timeout = timeout
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.timeout = timeout
|
||||||
|
return evolve(self, timeout=timeout)
|
||||||
|
|
||||||
|
def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient":
|
||||||
|
"""Manually set the underlying httpx.Client
|
||||||
|
|
||||||
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
||||||
|
"""
|
||||||
|
self._client = client
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_httpx_client(self) -> httpx.Client:
|
||||||
|
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
|
||||||
|
if self._client is None:
|
||||||
|
self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
|
||||||
|
self._client = httpx.Client(
|
||||||
|
base_url=self._base_url,
|
||||||
|
cookies=self._cookies,
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=self._timeout,
|
||||||
|
verify=self._verify_ssl,
|
||||||
|
follow_redirects=self._follow_redirects,
|
||||||
|
**self._httpx_args,
|
||||||
|
)
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def __enter__(self) -> "AuthenticatedClient":
|
||||||
|
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
|
||||||
|
self.get_httpx_client().__enter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
|
||||||
|
self.get_httpx_client().__exit__(*args, **kwargs)
|
||||||
|
|
||||||
|
def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "AuthenticatedClient":
|
||||||
|
"""Manually set the underlying httpx.AsyncClient
|
||||||
|
|
||||||
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
||||||
|
"""
|
||||||
|
self._async_client = async_client
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_async_httpx_client(self) -> httpx.AsyncClient:
|
||||||
|
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
|
||||||
|
if self._async_client is None:
|
||||||
|
self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
|
||||||
|
self._async_client = httpx.AsyncClient(
|
||||||
|
base_url=self._base_url,
|
||||||
|
cookies=self._cookies,
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=self._timeout,
|
||||||
|
verify=self._verify_ssl,
|
||||||
|
follow_redirects=self._follow_redirects,
|
||||||
|
**self._httpx_args,
|
||||||
|
)
|
||||||
|
return self._async_client
|
||||||
|
|
||||||
|
async def __aenter__(self) -> "AuthenticatedClient":
|
||||||
|
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
|
||||||
|
await self.get_async_httpx_client().__aenter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
|
||||||
|
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
|
||||||
16
utils/clients/gooyal_promotions_client/errors.py
Normal file
16
utils/clients/gooyal_promotions_client/errors.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"""Contains shared errors types that can be raised from API functions"""
|
||||||
|
|
||||||
|
|
||||||
|
class UnexpectedStatus(Exception):
|
||||||
|
"""Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True"""
|
||||||
|
|
||||||
|
def __init__(self, status_code: int, content: bytes):
|
||||||
|
self.status_code = status_code
|
||||||
|
self.content = content
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
f"Unexpected status code: {status_code}\n\nResponse content:\n{content.decode(errors='ignore')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["UnexpectedStatus"]
|
||||||
19
utils/clients/gooyal_promotions_client/models/__init__.py
Normal file
19
utils/clients/gooyal_promotions_client/models/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
"""Contains all the data models used in inputs/outputs"""
|
||||||
|
|
||||||
|
from .event import Event
|
||||||
|
from .paginated_promotion_list import PaginatedPromotionList
|
||||||
|
from .promote import Promote
|
||||||
|
from .promotion import Promotion
|
||||||
|
from .state_enum import StateEnum
|
||||||
|
from .user_plan import UserPlan
|
||||||
|
from .user_recipient import UserRecipient
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"Event",
|
||||||
|
"PaginatedPromotionList",
|
||||||
|
"Promote",
|
||||||
|
"Promotion",
|
||||||
|
"StateEnum",
|
||||||
|
"UserPlan",
|
||||||
|
"UserRecipient",
|
||||||
|
)
|
||||||
120
utils/clients/gooyal_promotions_client/models/event.py
Normal file
120
utils/clients/gooyal_promotions_client/models/event.py
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any, TypeVar, cast
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="Event")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class Event:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
label (str):
|
||||||
|
data (Any):
|
||||||
|
user (None | UUID):
|
||||||
|
application (None | UUID):
|
||||||
|
"""
|
||||||
|
|
||||||
|
label: str
|
||||||
|
data: Any
|
||||||
|
user: None | UUID
|
||||||
|
application: None | UUID
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
label = self.label
|
||||||
|
|
||||||
|
data = self.data
|
||||||
|
|
||||||
|
user: None | str
|
||||||
|
if isinstance(self.user, UUID):
|
||||||
|
user = str(self.user)
|
||||||
|
else:
|
||||||
|
user = self.user
|
||||||
|
|
||||||
|
application: None | str
|
||||||
|
if isinstance(self.application, UUID):
|
||||||
|
application = str(self.application)
|
||||||
|
else:
|
||||||
|
application = self.application
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"label": label,
|
||||||
|
"data": data,
|
||||||
|
"user": user,
|
||||||
|
"application": application,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
|
d = dict(src_dict)
|
||||||
|
label = d.pop("label")
|
||||||
|
|
||||||
|
data = d.pop("data")
|
||||||
|
|
||||||
|
def _parse_user(data: object) -> None | UUID:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
user_type_0 = UUID(data)
|
||||||
|
|
||||||
|
return user_type_0
|
||||||
|
except (TypeError, ValueError, AttributeError, KeyError):
|
||||||
|
pass
|
||||||
|
return cast(None | UUID, data)
|
||||||
|
|
||||||
|
user = _parse_user(d.pop("user"))
|
||||||
|
|
||||||
|
def _parse_application(data: object) -> None | UUID:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise TypeError()
|
||||||
|
application_type_0 = UUID(data)
|
||||||
|
|
||||||
|
return application_type_0
|
||||||
|
except (TypeError, ValueError, AttributeError, KeyError):
|
||||||
|
pass
|
||||||
|
return cast(None | UUID, data)
|
||||||
|
|
||||||
|
application = _parse_application(d.pop("application"))
|
||||||
|
|
||||||
|
event = cls(
|
||||||
|
label=label,
|
||||||
|
data=data,
|
||||||
|
user=user,
|
||||||
|
application=application,
|
||||||
|
)
|
||||||
|
|
||||||
|
event.additional_properties = d
|
||||||
|
return event
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..models.promotion import Promotion
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="PaginatedPromotionList")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class PaginatedPromotionList:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
count (int): Example: 123.
|
||||||
|
results (list[Promotion]):
|
||||||
|
next_ (None | str | Unset): Example: http://api.example.org/accounts/?offset=400&limit=100.
|
||||||
|
previous (None | str | Unset): Example: http://api.example.org/accounts/?offset=200&limit=100.
|
||||||
|
"""
|
||||||
|
|
||||||
|
count: int
|
||||||
|
results: list[Promotion]
|
||||||
|
next_: None | str | Unset = UNSET
|
||||||
|
previous: None | str | Unset = UNSET
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
count = self.count
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for results_item_data in self.results:
|
||||||
|
results_item = results_item_data.to_dict()
|
||||||
|
results.append(results_item)
|
||||||
|
|
||||||
|
next_: None | str | Unset
|
||||||
|
if isinstance(self.next_, Unset):
|
||||||
|
next_ = UNSET
|
||||||
|
else:
|
||||||
|
next_ = self.next_
|
||||||
|
|
||||||
|
previous: None | str | Unset
|
||||||
|
if isinstance(self.previous, Unset):
|
||||||
|
previous = UNSET
|
||||||
|
else:
|
||||||
|
previous = self.previous
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"count": count,
|
||||||
|
"results": results,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if next_ is not UNSET:
|
||||||
|
field_dict["next"] = next_
|
||||||
|
if previous is not UNSET:
|
||||||
|
field_dict["previous"] = previous
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
|
from ..models.promotion import Promotion
|
||||||
|
|
||||||
|
d = dict(src_dict)
|
||||||
|
count = d.pop("count")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
_results = d.pop("results")
|
||||||
|
for results_item_data in _results:
|
||||||
|
results_item = Promotion.from_dict(results_item_data)
|
||||||
|
|
||||||
|
results.append(results_item)
|
||||||
|
|
||||||
|
def _parse_next_(data: object) -> None | str | Unset:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
return cast(None | str | Unset, data)
|
||||||
|
|
||||||
|
next_ = _parse_next_(d.pop("next", UNSET))
|
||||||
|
|
||||||
|
def _parse_previous(data: object) -> None | str | Unset:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
return cast(None | str | Unset, data)
|
||||||
|
|
||||||
|
previous = _parse_previous(d.pop("previous", UNSET))
|
||||||
|
|
||||||
|
paginated_promotion_list = cls(
|
||||||
|
count=count,
|
||||||
|
results=results,
|
||||||
|
next_=next_,
|
||||||
|
previous=previous,
|
||||||
|
)
|
||||||
|
|
||||||
|
paginated_promotion_list.additional_properties = d
|
||||||
|
return paginated_promotion_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
91
utils/clients/gooyal_promotions_client/models/promote.py
Normal file
91
utils/clients/gooyal_promotions_client/models/promote.py
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import TYPE_CHECKING, Any, TypeVar
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..models.promotion import Promotion
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="Promote")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class Promote:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
promotions (list[Promotion]):
|
||||||
|
label (str):
|
||||||
|
data (Any):
|
||||||
|
"""
|
||||||
|
|
||||||
|
promotions: list[Promotion]
|
||||||
|
label: str
|
||||||
|
data: Any
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
promotions = []
|
||||||
|
for promotions_item_data in self.promotions:
|
||||||
|
promotions_item = promotions_item_data.to_dict()
|
||||||
|
promotions.append(promotions_item)
|
||||||
|
|
||||||
|
label = self.label
|
||||||
|
|
||||||
|
data = self.data
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"promotions": promotions,
|
||||||
|
"label": label,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
|
from ..models.promotion import Promotion
|
||||||
|
|
||||||
|
d = dict(src_dict)
|
||||||
|
promotions = []
|
||||||
|
_promotions = d.pop("promotions")
|
||||||
|
for promotions_item_data in _promotions:
|
||||||
|
promotions_item = Promotion.from_dict(promotions_item_data)
|
||||||
|
|
||||||
|
promotions.append(promotions_item)
|
||||||
|
|
||||||
|
label = d.pop("label")
|
||||||
|
|
||||||
|
data = d.pop("data")
|
||||||
|
|
||||||
|
promote = cls(
|
||||||
|
promotions=promotions,
|
||||||
|
label=label,
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
promote.additional_properties = d
|
||||||
|
return promote
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
129
utils/clients/gooyal_promotions_client/models/promotion.py
Normal file
129
utils/clients/gooyal_promotions_client/models/promotion.py
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
|
||||||
|
from ..models.state_enum import StateEnum
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..models.event import Event
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="Promotion")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class Promotion:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
uuid (UUID):
|
||||||
|
event (Event):
|
||||||
|
user (UUID):
|
||||||
|
promotion_amount (int | None | Unset):
|
||||||
|
state (StateEnum | Unset): * `1` - created
|
||||||
|
* `2` - delayed
|
||||||
|
* `3` - pending
|
||||||
|
* `4` - incomplete
|
||||||
|
* `5` - success
|
||||||
|
* `6` - failed
|
||||||
|
* `7` - expected_failure
|
||||||
|
"""
|
||||||
|
|
||||||
|
uuid: UUID
|
||||||
|
event: Event
|
||||||
|
user: UUID
|
||||||
|
promotion_amount: int | None | Unset = UNSET
|
||||||
|
state: StateEnum | Unset = UNSET
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
uuid = str(self.uuid)
|
||||||
|
|
||||||
|
event = self.event.to_dict()
|
||||||
|
|
||||||
|
user = str(self.user)
|
||||||
|
|
||||||
|
promotion_amount: int | None | Unset
|
||||||
|
if isinstance(self.promotion_amount, Unset):
|
||||||
|
promotion_amount = UNSET
|
||||||
|
else:
|
||||||
|
promotion_amount = self.promotion_amount
|
||||||
|
|
||||||
|
state: int | Unset = UNSET
|
||||||
|
if not isinstance(self.state, Unset):
|
||||||
|
state = self.state.value
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"uuid": uuid,
|
||||||
|
"event": event,
|
||||||
|
"user": user,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if promotion_amount is not UNSET:
|
||||||
|
field_dict["promotion_amount"] = promotion_amount
|
||||||
|
if state is not UNSET:
|
||||||
|
field_dict["state"] = state
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
|
from ..models.event import Event
|
||||||
|
|
||||||
|
d = dict(src_dict)
|
||||||
|
uuid = UUID(d.pop("uuid"))
|
||||||
|
|
||||||
|
event = Event.from_dict(d.pop("event"))
|
||||||
|
|
||||||
|
user = UUID(d.pop("user"))
|
||||||
|
|
||||||
|
def _parse_promotion_amount(data: object) -> int | None | Unset:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
if isinstance(data, Unset):
|
||||||
|
return data
|
||||||
|
return cast(int | None | Unset, data)
|
||||||
|
|
||||||
|
promotion_amount = _parse_promotion_amount(d.pop("promotion_amount", UNSET))
|
||||||
|
|
||||||
|
_state = d.pop("state", UNSET)
|
||||||
|
state: StateEnum | Unset
|
||||||
|
if isinstance(_state, Unset):
|
||||||
|
state = UNSET
|
||||||
|
else:
|
||||||
|
state = StateEnum(_state)
|
||||||
|
|
||||||
|
promotion = cls(
|
||||||
|
uuid=uuid,
|
||||||
|
event=event,
|
||||||
|
user=user,
|
||||||
|
promotion_amount=promotion_amount,
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
|
||||||
|
promotion.additional_properties = d
|
||||||
|
return promotion
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
14
utils/clients/gooyal_promotions_client/models/state_enum.py
Normal file
14
utils/clients/gooyal_promotions_client/models/state_enum.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class StateEnum(IntEnum):
|
||||||
|
VALUE_1 = 1
|
||||||
|
VALUE_2 = 2
|
||||||
|
VALUE_3 = 3
|
||||||
|
VALUE_4 = 4
|
||||||
|
VALUE_5 = 5
|
||||||
|
VALUE_6 = 6
|
||||||
|
VALUE_7 = 7
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
113
utils/clients/gooyal_promotions_client/models/user_plan.py
Normal file
113
utils/clients/gooyal_promotions_client/models/user_plan.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..models.user_recipient import UserRecipient
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="UserPlan")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class UserPlan:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
title (str):
|
||||||
|
banner (None | str):
|
||||||
|
description (str):
|
||||||
|
description_details (Any):
|
||||||
|
recipients (list[UserRecipient]):
|
||||||
|
"""
|
||||||
|
|
||||||
|
title: str
|
||||||
|
banner: None | str
|
||||||
|
description: str
|
||||||
|
description_details: Any
|
||||||
|
recipients: list[UserRecipient]
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
title = self.title
|
||||||
|
|
||||||
|
banner: None | str
|
||||||
|
banner = self.banner
|
||||||
|
|
||||||
|
description = self.description
|
||||||
|
|
||||||
|
description_details = self.description_details
|
||||||
|
|
||||||
|
recipients = []
|
||||||
|
for recipients_item_data in self.recipients:
|
||||||
|
recipients_item = recipients_item_data.to_dict()
|
||||||
|
recipients.append(recipients_item)
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"title": title,
|
||||||
|
"banner": banner,
|
||||||
|
"description": description,
|
||||||
|
"description_details": description_details,
|
||||||
|
"recipients": recipients,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
|
from ..models.user_recipient import UserRecipient
|
||||||
|
|
||||||
|
d = dict(src_dict)
|
||||||
|
title = d.pop("title")
|
||||||
|
|
||||||
|
def _parse_banner(data: object) -> None | str:
|
||||||
|
if data is None:
|
||||||
|
return data
|
||||||
|
return cast(None | str, data)
|
||||||
|
|
||||||
|
banner = _parse_banner(d.pop("banner"))
|
||||||
|
|
||||||
|
description = d.pop("description")
|
||||||
|
|
||||||
|
description_details = d.pop("description_details")
|
||||||
|
|
||||||
|
recipients = []
|
||||||
|
_recipients = d.pop("recipients")
|
||||||
|
for recipients_item_data in _recipients:
|
||||||
|
recipients_item = UserRecipient.from_dict(recipients_item_data)
|
||||||
|
|
||||||
|
recipients.append(recipients_item)
|
||||||
|
|
||||||
|
user_plan = cls(
|
||||||
|
title=title,
|
||||||
|
banner=banner,
|
||||||
|
description=description,
|
||||||
|
description_details=description_details,
|
||||||
|
recipients=recipients,
|
||||||
|
)
|
||||||
|
|
||||||
|
user_plan.additional_properties = d
|
||||||
|
return user_plan
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
|
from attrs import define as _attrs_define
|
||||||
|
from attrs import field as _attrs_field
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="UserRecipient")
|
||||||
|
|
||||||
|
|
||||||
|
@_attrs_define
|
||||||
|
class UserRecipient:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
label (str):
|
||||||
|
base_amount_field (str):
|
||||||
|
"""
|
||||||
|
|
||||||
|
label: str
|
||||||
|
base_amount_field: str
|
||||||
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict[str, Any]:
|
||||||
|
label = self.label
|
||||||
|
|
||||||
|
base_amount_field = self.base_amount_field
|
||||||
|
|
||||||
|
field_dict: dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"label": label,
|
||||||
|
"base_amount_field": base_amount_field,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
||||||
|
d = dict(src_dict)
|
||||||
|
label = d.pop("label")
|
||||||
|
|
||||||
|
base_amount_field = d.pop("base_amount_field")
|
||||||
|
|
||||||
|
user_recipient = cls(
|
||||||
|
label=label,
|
||||||
|
base_amount_field=base_amount_field,
|
||||||
|
)
|
||||||
|
|
||||||
|
user_recipient.additional_properties = d
|
||||||
|
return user_recipient
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> list[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
1
utils/clients/gooyal_promotions_client/py.typed
Normal file
1
utils/clients/gooyal_promotions_client/py.typed
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# Marker file for PEP 561
|
||||||
54
utils/clients/gooyal_promotions_client/types.py
Normal file
54
utils/clients/gooyal_promotions_client/types.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
"""Contains some shared types for properties"""
|
||||||
|
|
||||||
|
from collections.abc import Mapping, MutableMapping
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import IO, BinaryIO, Generic, Literal, TypeVar
|
||||||
|
|
||||||
|
from attrs import define
|
||||||
|
|
||||||
|
|
||||||
|
class Unset:
|
||||||
|
def __bool__(self) -> Literal[False]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
UNSET: Unset = Unset()
|
||||||
|
|
||||||
|
# The types that `httpx.Client(files=)` can accept, copied from that library.
|
||||||
|
FileContent = IO[bytes] | bytes | str
|
||||||
|
FileTypes = (
|
||||||
|
# (filename, file (or bytes), content_type)
|
||||||
|
tuple[str | None, FileContent, str | None]
|
||||||
|
# (filename, file (or bytes), content_type, headers)
|
||||||
|
| tuple[str | None, FileContent, str | None, Mapping[str, str]]
|
||||||
|
)
|
||||||
|
RequestFiles = list[tuple[str, FileTypes]]
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class File:
|
||||||
|
"""Contains information for file uploads"""
|
||||||
|
|
||||||
|
payload: BinaryIO
|
||||||
|
file_name: str | None = None
|
||||||
|
mime_type: str | None = None
|
||||||
|
|
||||||
|
def to_tuple(self) -> FileTypes:
|
||||||
|
"""Return a tuple representation that httpx will accept for multipart/form-data"""
|
||||||
|
return self.file_name, self.payload, self.mime_type
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class Response(Generic[T]):
|
||||||
|
"""A response from an endpoint"""
|
||||||
|
|
||||||
|
status_code: HTTPStatus
|
||||||
|
content: bytes
|
||||||
|
headers: MutableMapping[str, str]
|
||||||
|
parsed: T | None
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["UNSET", "File", "FileTypes", "RequestFiles", "Response", "Unset"]
|
||||||
88
utils/clients/promotions_client.py
Normal file
88
utils/clients/promotions_client.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.db import models
|
||||||
|
from httpx import Request
|
||||||
|
|
||||||
|
from utils.clients.gooyal_promotions_client import AuthenticatedClient
|
||||||
|
from utils.clients.gooyal_promotions_client.api.api import (
|
||||||
|
api_v2_promotions_application_plan_promote_create
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def login_as_client_credentials():
|
||||||
|
access_token_cache_key = 'accounts_access_token'
|
||||||
|
access_token = cache.get(access_token_cache_key)
|
||||||
|
# expires_in = self.auth_data.get('expires_in')
|
||||||
|
# created_at = self.auth_data.get('created_at')
|
||||||
|
#
|
||||||
|
# if access_token and expires_in > time.time() - created_at:
|
||||||
|
# return self.auth_data
|
||||||
|
if access_token:
|
||||||
|
logger.debug('token available in cache')
|
||||||
|
return access_token
|
||||||
|
|
||||||
|
logger.debug('no token available in cache')
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"grant_type": 'client_credentials',
|
||||||
|
"scope": settings.OAUTH2_PROVIDER_SCOPES,
|
||||||
|
}
|
||||||
|
auth = (settings.OAUTH2_PROVIDER_CLIENT_ID, settings.OAUTH2_PROVIDER_CLIENT_SECRET)
|
||||||
|
|
||||||
|
response = requests.post(f'{settings.OAUTH2_PROVIDER_BASE_PUBLIC_URL}/token/',
|
||||||
|
data=data,
|
||||||
|
auth=auth)
|
||||||
|
logger.debug(response.content)
|
||||||
|
|
||||||
|
auth_data = response.json()
|
||||||
|
if 'access_token' in auth_data:
|
||||||
|
auth_data['created_at'] = time.time()
|
||||||
|
cache.set(access_token_cache_key, auth_data, timeout=auth_data.get('expires_in') - 5)
|
||||||
|
|
||||||
|
return auth_data
|
||||||
|
|
||||||
|
|
||||||
|
def log_request(request: Request):
|
||||||
|
logger.debug(f'authorization: {request.headers["authorization"]}')
|
||||||
|
logger.debug(
|
||||||
|
f"Request event hook: {request.method} {request.url} {request.method} {request.headers} - Waiting for response")
|
||||||
|
|
||||||
|
|
||||||
|
def log_response(response):
|
||||||
|
request = response.request
|
||||||
|
logger.debug(f"Response event hook: {request.method} {request.url} - Status {response.status_code}")
|
||||||
|
# logger.debug(f"Response event hook: {response.read()}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_promotions_client():
|
||||||
|
access_token = login_as_client_credentials()['access_token']
|
||||||
|
client = AuthenticatedClient(base_url=settings.PROMOTIONS_BASE_PUBLIC_URL, token=access_token,
|
||||||
|
httpx_args={"event_hooks": {"request": [log_request], "response": [log_response]}},
|
||||||
|
)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
def promote_user(user, plan, label, data):
|
||||||
|
class Tmp():
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"data": data,
|
||||||
|
"label": label
|
||||||
|
}
|
||||||
|
|
||||||
|
data = Tmp()
|
||||||
|
client = get_promotions_client()
|
||||||
|
response = api_v2_promotions_application_plan_promote_create.sync(
|
||||||
|
user_uuid=user,
|
||||||
|
id=plan,
|
||||||
|
client=client,
|
||||||
|
body=data
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
@ -23,14 +23,9 @@ def exception_handler(exc, context):
|
||||||
|
|
||||||
if isinstance(exc, APIException):
|
if isinstance(exc, APIException):
|
||||||
try:
|
try:
|
||||||
if isinstance(exc.detail, dict):
|
error = getattr(getattr(exc, 'detail', None), 'code', None) or getattr(exc, 'code', None)
|
||||||
if 'code' in exc.detail:
|
if not error:
|
||||||
error = exc.detail['code']
|
error = getattr(exc, 'default_code')
|
||||||
else:
|
|
||||||
attr = next(iter(exc.detail))
|
|
||||||
error = exc.detail[attr].code
|
|
||||||
else:
|
|
||||||
error = exc.detail.code or exc.code
|
|
||||||
except:
|
except:
|
||||||
error = ''
|
error = ''
|
||||||
|
|
||||||
|
|
@ -46,10 +41,7 @@ def exception_handler(exc, context):
|
||||||
# message = error
|
# message = error
|
||||||
#
|
#
|
||||||
# response_data['message'] = message
|
# response_data['message'] = message
|
||||||
if isinstance(exc.detail, dict):
|
response_data['details'] = {"message": exc.detail}
|
||||||
response_data['details'] = exc.detail
|
|
||||||
else:
|
|
||||||
response_data['details'] = {"message": exc.detail}
|
|
||||||
response_data['details']['error'] = error
|
response_data['details']['error'] = error
|
||||||
response_data['details']['timestamp'] = timezone.now().isoformat()
|
response_data['details']['timestamp'] = timezone.now().isoformat()
|
||||||
|
|
||||||
|
|
@ -89,7 +81,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class UnprocessableEntity(APIException):
|
class UnprocessableEntity(APIException):
|
||||||
status_code = 422
|
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||||
default_detail = 'The request was well-formed but cannot be processed due to semantic errors.'
|
default_detail = 'The request was well-formed but cannot be processed due to semantic errors.'
|
||||||
default_code = 'unprocessable_entity'
|
default_code = 'unprocessable_entity'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,33 @@ class RequestOTPDayRateThrottle(SimpleRateThrottle):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RequestOTPMinRateThrottle(RequestOTPDayRateThrottle):
|
class RequestOTPMinRateThrottle(RequestOTPDayRateThrottle):
|
||||||
scope = 'otp_min'
|
scope = 'otp_min'
|
||||||
|
|
||||||
|
|
||||||
|
class NumberedRequestOTPDayRateThrottle(SimpleRateThrottle):
|
||||||
|
scope = 'numbered_otp_day'
|
||||||
|
|
||||||
|
def get_ident(self, request):
|
||||||
|
return request.data.get('phone_number')
|
||||||
|
|
||||||
|
def get_cache_key(self, request, view):
|
||||||
|
if request.user and request.user.is_authenticated:
|
||||||
|
ident = request.user.pk
|
||||||
|
else:
|
||||||
|
ident = self.get_ident(request)
|
||||||
|
|
||||||
|
return self.cache_format % {
|
||||||
|
'scope': self.scope,
|
||||||
|
'ident': ident
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class NumberedRequestOTPMinRateThrottle(NumberedRequestOTPDayRateThrottle):
|
||||||
|
scope = 'numbered_otp_min'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue