This repository has been archived on 2024-09-18. You can view files and clone it, but cannot push or open issues or pull requests.
arka-api/api/api_params.py

291 lines
11 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
from .api_errors import *
from .models import AccessToken
import re
def _make_invalid_argument_type_error(name, value, except_type):
related = {"param_name": name, "excepted_type": except_type, "value": value}
raise Exception(API_ERROR_INVALID_ARGUMENT_TYPE, related)
def _make_invalid_argument_value_error(name, value, err_code, message):
related = {"param_name": name, "value": value, "error_code": err_code, "message": message}
raise Exception(API_ERROR_INVALID_ARGUMENT_VALUE, related)
class ApiParam:
def __init__(self, name, description, required=True, default=None):
self.name = name
self.description = description
self.required = required
self.default = default
def validate(self, value):
"""
Валидация параметра, в случае ошибки нужно выбросить исключение, соответствующее ошибке
в случае успеха нужно вернуть объект, который и будет передан в метод в качестве параметра
"""
raise Exception(API_ERROR_INTERNAL_ERROR, f"param {self.name} have invalid definition (defined as super class)")
def get_doc(self):
return self.description
def get_name(self):
return self.name
def is_required(self):
return self.required
def get_type_name(self):
return f"{type(self)}"
def to_json(self):
return {
"name": self.get_name(),
"type": self.get_type_name(),
"description": self.get_doc(),
"required": self.is_required()
}
def __str__(self):
return f"{type(self)}: name={self.name}"
# Специальный класс параметра, нужен для получения доступа к raw request
class ApiRequestParam(ApiParam):
def __init__(self, name="request", description=None, **kwargs):
super().__init__(name=name, description=description, **kwargs)
def validate(self, value):
return value
def get_type_name(self):
return "__internal_request"
def get_doc(self):
return None
def to_json(self):
return None
class ApiParamStr(ApiParam):
def __init__(self, regex=None, max_length=None, min_length=None, **kwargs):
super().__init__(**kwargs)
self.regex = None if regex is None else re.compile(regex)
self.max_length = max_length
self.min_length = min_length
def validate(self, value):
if value is None:
if self.required:
raise Exception(API_ERROR_MISSING_ARGUMENT, self.name)
else:
# вернуть значение по умолчанию без дополнительной проверки
return self.default
if self.regex is not None:
if not self.regex.match(value):
_make_invalid_argument_value_error(self.name, value, "invalid",
f"expected string like this python regex: '{self.regex.pattern}'")
if self.max_length is not None:
if len(value) > self.max_length:
_make_invalid_argument_value_error(self.name, value, "long",
f"too long string. max size is '{self.max_length}' char(s)")
if self.min_length is not None:
if len(value) < self.min_length:
_make_invalid_argument_value_error(self.name, value, "short",
f"too short string. min size is '{self.max_length}' char(s)")
return value
def get_type_name(self):
return "String"
class ApiParamInt(ApiParam):
def __init__(self, value_min=None, value_max=None, **kwargs):
super().__init__(**kwargs)
self.value_max = value_max
self.value_min = value_min
def _check_min_max(self, value):
if self.value_min is not None:
if self.value_min > value:
_make_invalid_argument_value_error(self.name, value, 'out_of_range',
f'the minimum value for this param is {self.value_min}')
if self.value_max is not None:
if self.value_max < value:
_make_invalid_argument_value_error(self.name, value, 'out_of_range',
f'the maximum value for this param is {self.value_max}')
def validate(self, value):
if value is None:
if self.required:
raise Exception(API_ERROR_MISSING_ARGUMENT, self.name)
else:
# вернуть значение по умолчанию без дополнительной проверки
return self.default
try:
value = int(value)
except Exception:
_make_invalid_argument_type_error(self.name, value, self.get_type_name())
self._check_min_max(value)
return value
def get_doc(self):
return super().get_doc().replace('{min}', str(self.value_min)).replace('{max}', str(self.value_max))
def get_type_name(self):
return "Int"
class ApiParamEnum(ApiParam):
def __init__(self, choices, **kwargs):
super().__init__(**kwargs)
if not callable(choices):
self.choices = []
for c in choices:
self.choices.append(
(c[0], c[1])
)
else:
if asyncio.iscoroutinefunction(choices):
loop = asyncio.get_event_loop()
coroutine = choices()
loop.run_until_complete(coroutine)
else:
self.choices = choices()
async def validate(self, value):
if value is None:
if self.required:
raise Exception(API_ERROR_MISSING_ARGUMENT, self.name)
else:
# вернуть значение по умолчанию без дополнительной проверки
return self.default
for choice in self.choices:
cmp = choice[0] if type(choice[0]) is str else str(choice[0])
if cmp == value:
return choice[0]
_make_invalid_argument_value_error(self.name, value, 'out_of_range',
f'expected value one of {[c[0] for c in self.choices]}')
def get_doc(self):
doc = super().get_doc()
ch = "<ul>"
for c in self.choices:
if c[0] != '':
ch += f"<li>{c[0]} - {c[1]}</li>"
ch += "</ul>"
return doc.replace('{choices}', ch)
def get_type_name(self):
return "Enum"
class ApiParamBoolean(ApiParam):
def __init__(self, **kwargs):
super().__init__( **kwargs)
def validate(self, value):
if value is None:
if self.required:
raise Exception(API_ERROR_MISSING_ARGUMENT, self.name)
else:
# вернуть значение по умолчанию без дополнительной проверки
return self.default
if value == 'true' or value == '1' or value == 'y':
return True
elif value == 'false' or value == '0' or value == 'n':
return False
else:
_make_invalid_argument_value_error(self.name, value, "invalid", "expected (true|y|1) or (false|n|0)")
def get_type_name(self):
return "Boolean"
class ApiParamFloat(ApiParamInt):
def validate(self, value):
if value is None:
if self.required:
raise Exception(API_ERROR_MISSING_ARGUMENT, self.name)
else:
# вернуть значение по умолчанию без дополнительной проверки
return self.default
try:
value = float(value)
except Exception:
_make_invalid_argument_type_error(self.name, value, self.get_type_name())
self._check_min_max(value)
return value
def get_doc(self):
return super().get_doc().replace('{min}', str(self.value_min)).replace('{max}', str(self.value_max))
def get_type_name(self):
return "Float"
class ApiParamAccessToken(ApiParam):
def __init__(self, regex=None, name="access_token",
description="<i>Токен</i>, выданный методом <code>account.auth</code>", **kwargs):
super().__init__(name=name, description=description, **kwargs)
self.regex = (lambda: None if regex is None else re.compile(regex))
async def validate(self, value):
if value is None:
if self.required:
raise Exception(API_ERROR_MISSING_ARGUMENT, self.name)
else:
# вернуть None, потому что параметра нет
return None
return await AccessToken.get_by_token(value)
def get_type_name(self):
return "AccessToken"
class ApiParamPassword(ApiParamStr):
def __init__(self, name="password", description="Пароль пользователя", **kwargs):
super().__init__(name=name, description=description, regex="^[ -~а-яА-Я]{6,}$", **kwargs)
class ApiParamPhone(ApiParamStr):
def __init__(self, name="phone", description="Телефон в формате <code>[[+]7]1112223333</code> "
"<i>(в квадратных скобках необязательная часть)</i>", **kwargs):
super().__init__(name=name, description=description, regex="^((\\+7)|(7)|)[0-9]{10}$", **kwargs)
def validate(self, value):
value = super(ApiParamPhone, self).validate(value)
if value is not None:
# Гоша попросил запилить фичу, чтобы принимались номера:
# +79991112233
# 79991112233
# 9991112233
if re.match("^[0-9]{10}$", value) is not None:
return f"+7{value}"
elif re.match("^7[0-9]{10}$", value) is not None:
return f"+{value}"
return value
class ApiParamVerifyCode(ApiParamInt):
def __init__(self, name="code",
description="Код верификации (требуется если клиенту будет отправлена "
"одна из ошибок <i>верификации</i>)", **kwargs):
super().__init__(name=name, required=False, value_min=0, value_max=9999, description=description, **kwargs)