Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Рефакторинг моделей; улучшена аннотация типов #658

Merged
merged 7 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# -- Project information -----------------------------------------------------

project = 'Yandex Music API'
copyright = '2019-2023 Ilya (Marshal) <https://github.com/MarshalX>'
copyright = '2019-2024 Ilya (Marshal) <https://github.com/MarshalX>'
author = 'Ilya (Marshal)'

language = 'en'
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
requests
aiohttp
aiofiles
typing-extensions
# rly dev
ruff==0.1.6
coverage
Expand Down
4 changes: 3 additions & 1 deletion ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ ignore = [
"PGH004", # use specific rule code with noqa; works bad with JetBrains IDE Warnings
"ANN002", # Missing type annotation for `*args`
"ANN003", # Missing type annotation for `**kwargs`
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed in `*args`
"ANN101", # Missing type annotation for `self` in method
"ANN102", # Missing type annotation for `cls` in classmethod
"D203", # we are not using blank line before class
"D213", # we are using first line for summary
"D406", "D407", # we are using google style docstring
"S101", # Use of `assert` detected
"PGH003", # Use specific rule codes when ignoring type issue
]

[per-file-ignores]
Expand All @@ -48,7 +51,6 @@ ignore = [
]
"yandex_music/__init__.py" = ["I001"] # Import sort
"yandex_music/client*.py" = ["T201"] # print
"yandex_music/utils/request*.py" = ["ANN"] # TODO(MarshalX): annotate and remove this ignore
"tests/*.py" = ["S101", "ANN", "D"]
"tests/__init__.py" = ["F401"] # Unused import
"test.py" = ["S101", "ERA001", "T201", "E501", "F401", "F841"]
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def run_tests(self) -> None:
long_description=readme,
long_description_content_type='text/markdown',
packages=find_packages(),
install_requires=['requests[socks]', 'aiohttp', 'aiofiles'],
install_requires=['requests[socks]', 'aiohttp', 'aiofiles', 'typing-extensions'],
include_package_data=True,
classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down
8 changes: 6 additions & 2 deletions yandex_music/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
__version__ = '2.2.0'
__license__ = 'GNU Lesser General Public License v3 (LGPLv3)'
__copyright__ = 'Copyright (C) 2019-2023 Ilya (Marshal) <https://github.com/MarshalX>'
__copyright__ = 'Copyright (C) 2019-2024 Ilya (Marshal) <https://github.com/MarshalX>'

from .base import YandexMusicObject
from .base import ClientType, YandexMusicObject, YandexMusicModel, JSONType, MapTypeToDeJson

from .settings import Settings
from .permission_alerts import PermissionAlerts
Expand Down Expand Up @@ -147,7 +147,11 @@
'__copyright__',
'__license__',
'__version__',
'ClientType',
'YandexMusicObject',
'YandexMusicModel',
'JSONType',
'MapTypeToDeJson',
'Client',
'ClientAsync',
'Account',
Expand Down
25 changes: 13 additions & 12 deletions yandex_music/account/account.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from dataclasses import field
from typing import TYPE_CHECKING, List, Optional

from yandex_music import YandexMusicObject
from yandex_music import YandexMusicModel
from yandex_music.utils import model

if TYPE_CHECKING:
from yandex_music import Client, PassportPhone
from yandex_music import ClientType, JSONType, PassportPhone


@model
class Account(YandexMusicObject):
class Account(YandexMusicModel):
"""Класс, представляющий основную информацию об аккаунте пользователя.

Attributes:
Expand Down Expand Up @@ -43,18 +44,18 @@ class Account(YandexMusicObject):
display_name: Optional[str] = None
hosted_user: Optional[bool] = None
birthday: Optional[str] = None
passport_phones: List['PassportPhone'] = None
passport_phones: List['PassportPhone'] = field(default_factory=list)
registered_at: Optional[str] = None
has_info_for_app_metrica: bool = None
child: bool = None
client: Optional['Client'] = None
has_info_for_app_metrica: Optional[bool] = None
child: Optional[bool] = None
client: Optional['ClientType'] = None

def __post_init__(self) -> None:
if self.uid:
self._id_attrs = (self.uid,)

@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['Account']:
def de_json(cls, data: 'JSONType', client: 'ClientType') -> Optional['Account']:
"""Десериализация объекта.

Args:
Expand All @@ -64,12 +65,12 @@ def de_json(cls, data: dict, client: 'Client') -> Optional['Account']:
Returns:
:obj:`yandex_music.Account`: Основная информация об аккаунте пользователя.
"""
if not cls.is_valid_model_data(data):
if not cls.is_dict_model_data(data):
return None

data = super(Account, cls).de_json(data, client)
cls_data = cls.cleanup_data(data, client)
from yandex_music import PassportPhone

data['passport_phones'] = PassportPhone.de_list(data.get('passport_phones'), client)
cls_data['passport_phones'] = PassportPhone.de_list(data.get('passport_phones'), client)

return cls(client=client, **data)
return cls(client=client, **cls_data) # type: ignore
18 changes: 9 additions & 9 deletions yandex_music/account/alert.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from typing import TYPE_CHECKING, Optional

from yandex_music import YandexMusicObject
from yandex_music import YandexMusicModel
from yandex_music.utils import model

if TYPE_CHECKING:
from yandex_music import AlertButton, Client
from yandex_music import AlertButton, ClientType, JSONType


@model
class Alert(YandexMusicObject):
class Alert(YandexMusicModel):
"""Класс, представляющий блок с предупреждением.

Note:
Expand All @@ -35,13 +35,13 @@ class Alert(YandexMusicObject):
alert_type: str
button: 'AlertButton'
close_button: bool
client: Optional['Client'] = None
client: Optional['ClientType'] = None

def __post_init__(self) -> None:
self._id_attrs = (self.alert_id,)

@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['Alert']:
def de_json(cls, data: 'JSONType', client: 'ClientType') -> Optional['Alert']:
"""Десериализация объекта.

Args:
Expand All @@ -51,12 +51,12 @@ def de_json(cls, data: dict, client: 'Client') -> Optional['Alert']:
Returns:
:obj:`yandex_music.Alert`: Сообщение о статусе подписки.
"""
if not cls.is_valid_model_data(data):
if not cls.is_dict_model_data(data):
return None

from yandex_music import AlertButton

data = super(Alert, cls).de_json(data, client)
data['button'] = AlertButton.de_json(data.get('button'), client)
cls_data = cls.cleanup_data(data, client)
cls_data['button'] = AlertButton.de_json(data.get('button'), client)

return cls(client=client, **data)
return cls(client=client, **cls_data) # type: ignore
26 changes: 4 additions & 22 deletions yandex_music/account/alert_button.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from typing import TYPE_CHECKING, Optional

from yandex_music import YandexMusicObject
from yandex_music import YandexMusicModel
from yandex_music.utils import model

if TYPE_CHECKING:
from yandex_music import Client
from yandex_music import ClientType


@model
class AlertButton(YandexMusicObject):
class AlertButton(YandexMusicModel):
"""Класс, представляющий кнопку в предупреждении.

Attributes:
Expand All @@ -23,25 +23,7 @@ class AlertButton(YandexMusicObject):
bg_color: str
text_color: str
uri: str
client: Optional['Client'] = None
client: Optional['ClientType'] = None

def __post_init__(self) -> None:
self._id_attrs = (self.text, self.uri)

@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['AlertButton']:
"""Десериализация объекта.

Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.

Returns:
:obj:`yandex_music.AlertButton`: Кнопка в статусе о подписки.
"""
if not cls.is_valid_model_data(data):
return None

data = super(AlertButton, cls).de_json(data, client)

return cls(client=client, **data)
38 changes: 11 additions & 27 deletions yandex_music/account/auto_renewable.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from typing import TYPE_CHECKING, List, Optional
from typing import TYPE_CHECKING, Optional

from yandex_music import YandexMusicObject
from yandex_music import YandexMusicModel
from yandex_music.utils import model

if TYPE_CHECKING:
from yandex_music import Client, Product, User
from yandex_music import ClientType, JSONType, Product, User


@model
class AutoRenewable(YandexMusicObject):
class AutoRenewable(YandexMusicModel):
"""Класс, представляющий информацию об автопродлении подписки.

Attributes:
Expand All @@ -31,13 +31,13 @@ class AutoRenewable(YandexMusicObject):
master_info: Optional['User'] = None
product_id: Optional[str] = None
order_id: Optional[int] = None
client: Optional['Client'] = None
client: Optional['ClientType'] = None

def __post_init__(self) -> None:
self._id_attrs = (self.expires, self.vendor, self.vendor_help_url, self.product, self.finished)

@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['AutoRenewable']:
def de_json(cls, data: 'JSONType', client: 'ClientType') -> Optional['AutoRenewable']:
"""Десериализация объекта.

Args:
Expand All @@ -47,29 +47,13 @@ def de_json(cls, data: dict, client: 'Client') -> Optional['AutoRenewable']:
Returns:
:obj:`yandex_music.AutoRenewable`: Информация об автопродлении подписки.
"""
if not cls.is_valid_model_data(data):
if not cls.is_dict_model_data(data):
return None

data = super(AutoRenewable, cls).de_json(data, client)
cls_data = cls.cleanup_data(data, client)
from yandex_music import Product, User

data['product'] = Product.de_json(data.get('product'), client)
data['master_info'] = User.de_json(data.get('master_info'), client)
cls_data['product'] = Product.de_json(data.get('product'), client)
cls_data['master_info'] = User.de_json(data.get('master_info'), client)

return cls(client=client, **data)

@classmethod
def de_list(cls, data: list, client: 'Client') -> List['AutoRenewable']:
"""Десериализация списка объектов.

Args:
data (:obj:`list`): Список словарей с полями и значениями десериализуемого объекта.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.

Returns:
:obj:`list` из :obj:`yandex_music.AutoRenewable`: Информация об автопродлении подписки.
"""
if not cls.is_valid_model_data(data, array=True):
return []

return [cls.de_json(auto_renewable, client) for auto_renewable in data]
return cls(client=client, **cls_data) # type: ignore
44 changes: 5 additions & 39 deletions yandex_music/account/deactivation.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from typing import TYPE_CHECKING, List, Optional
from typing import TYPE_CHECKING, Optional

from yandex_music import YandexMusicObject
from yandex_music import YandexMusicModel
from yandex_music.utils import model

if TYPE_CHECKING:
from yandex_music import Client
from yandex_music import ClientType


@model
class Deactivation(YandexMusicObject):
class Deactivation(YandexMusicModel):
"""Класс, представляющий способы деактивации мобильной услуги.

Note:
Expand All @@ -22,41 +22,7 @@ class Deactivation(YandexMusicObject):

method: str
instructions: Optional[str] = None
client: Optional['Client'] = None
client: Optional['ClientType'] = None

def __post_init__(self) -> None:
self._id_attrs = (self.method, self.instructions)

@classmethod
def de_json(cls, data: dict, client: 'Client') -> Optional['Deactivation']:
"""Десериализация объекта.

Args:
data (:obj:`dict`): Поля и значения десериализуемого объекта.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.

Returns:
:obj:`yandex_music.Deactivation`: Способ отключения услуги.
"""
if not cls.is_valid_model_data(data):
return None

data = super(Deactivation, cls).de_json(data, client)

return cls(client=client, **data)

@classmethod
def de_list(cls, data: list, client: 'Client') -> List['Deactivation']:
"""Десериализация списка объектов.

Args:
data (:obj:`list`): Список словарей с полями и значениями десериализуемого объекта.
client (:obj:`yandex_music.Client`, optional): Клиент Yandex Music.

Returns:
:obj:`list` из :obj:`yandex_music.Deactivation`: Способы отключения услуги.
"""
if not cls.is_valid_model_data(data, array=True):
return []

return [cls.de_json(deactivation, client) for deactivation in data]
Loading
Loading