Telegram Bot API 4.5

This commit is contained in:
Alex Root Junior 2020-01-11 19:47:39 +02:00
parent d2606b389a
commit ffb0cdf88f
39 changed files with 146 additions and 37 deletions

View file

@ -2,7 +2,7 @@
[![MIT License](https://img.shields.io/pypi/l/aiogram.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![Supported python versions](https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api)
[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-4.5-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api)
[![PyPi Package Version](https://img.shields.io/pypi/v/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![PyPi status](https://img.shields.io/pypi/status/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![Downloads](https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)

View file

@ -26,5 +26,5 @@ __all__ = (
"handler",
)
__version__ = "3.0.0a0"
__api_version__ = "4.4"
__version__ = "3.0.0a1"
__api_version__ = "4.5"

View file

@ -55,6 +55,7 @@ from ..methods import (
SendVideo,
SendVideoNote,
SendVoice,
SetChatAdministratorCustomTitle,
SetChatDescription,
SetChatPermissions,
SetChatPhoto,
@ -1204,6 +1205,27 @@ class Bot(BaseBot):
)
return await self.emit(call)
async def set_chat_administrator_custom_title(
self, chat_id: Union[int, str], user_id: int, custom_title: str,
) -> bool:
"""
Use this method to set a custom title for an administrator in a supergroup promoted by the
bot. Returns True on success.
Source: https://core.telegram.org/bots/api#setchatadministratorcustomtitle
:param chat_id: Unique identifier for the target chat or username of the target supergroup
(in the format @supergroupusername)
:param user_id: Unique identifier of the target user
:param custom_title: New custom title for the administrator; 0-16 characters, emoji are
not allowed
:return: Returns True on success.
"""
call = SetChatAdministratorCustomTitle(
chat_id=chat_id, user_id=user_id, custom_title=custom_title,
)
return await self.emit(call)
async def set_chat_permissions(
self, chat_id: Union[int, str], permissions: ChatPermissions
) -> bool:

View file

@ -50,6 +50,7 @@ from .send_venue import SendVenue
from .send_video import SendVideo
from .send_video_note import SendVideoNote
from .send_voice import SendVoice
from .set_chat_administrator_custom_title import SetChatAdministratorCustomTitle
from .set_chat_description import SetChatDescription
from .set_chat_permissions import SetChatPermissions
from .set_chat_photo import SetChatPhoto
@ -97,6 +98,7 @@ __all__ = (
"UnbanChatMember",
"RestrictChatMember",
"PromoteChatMember",
"SetChatAdministratorCustomTitle",
"SetChatPermissions",
"ExportChatInviteLink",
"SetChatPhoto",

View file

@ -0,0 +1,27 @@
from typing import Any, Dict, Union
from .base import Request, TelegramMethod
class SetChatAdministratorCustomTitle(TelegramMethod[bool]):
"""
Use this method to set a custom title for an administrator in a supergroup promoted by the
bot. Returns True on success.
Source: https://core.telegram.org/bots/api#setchatadministratorcustomtitle
"""
__returning__ = bool
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target supergroup (in the format
@supergroupusername)"""
user_id: int
"""Unique identifier of the target user"""
custom_title: str
"""New custom title for the administrator; 0-16 characters, emoji are not allowed"""
def build_request(self) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="setChatAdministratorCustomTitle", data=data)

View file

@ -16,7 +16,10 @@ class Animation(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
width: int
"""Video width as defined by sender"""
height: int

View file

@ -16,7 +16,10 @@ class Audio(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
duration: int
"""Duration of the audio in seconds as defined by sender"""
performer: Optional[str] = None

View file

@ -5,8 +5,8 @@ from typing import TYPE_CHECKING, Optional
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .chat_photo import ChatPhoto
from .message import Message
from .chat_photo import ChatPhoto
from .chat_permissions import ChatPermissions
@ -44,6 +44,9 @@ class Chat(TelegramObject):
"""Pinned message, for groups, supergroups and channels. Returned only in getChat."""
permissions: Optional[ChatPermissions] = None
"""Default chat member permissions, for groups and supergroups. Returned only in getChat."""
slow_mode_delay: Optional[int] = None
"""For supergroups, the minimum allowed delay between consecutive messages sent by each
unpriviledged user. Returned only in getChat."""
sticker_set_name: Optional[str] = None
"""For supergroups, name of group sticker set. Returned only in getChat."""
can_set_sticker_set: Optional[bool] = None

View file

@ -21,6 +21,8 @@ class ChatMember(TelegramObject):
status: str
"""The member's status in the chat. Can be 'creator', 'administrator', 'member', 'restricted',
'left' or 'kicked'"""
custom_title: Optional[str] = None
"""Owner and administrators only. Custom title for this user"""
until_date: Optional[Union[int, datetime.datetime, datetime.timedelta]] = None
"""Restricted and kicked only. Date when restrictions will be lifted for this user; unix time"""
can_be_edited: Optional[bool] = None

View file

@ -13,6 +13,12 @@ class ChatPhoto(TelegramObject):
small_file_id: str
"""File identifier of small (160x160) chat photo. This file_id can be used only for photo
download and only for as long as the photo is not changed."""
small_file_unique_id: str
"""Unique file identifier of small (160x160) chat photo, which is supposed to be the same over
time and for different bots. Can't be used to download or reuse the file."""
big_file_id: str
"""File identifier of big (640x640) chat photo. This file_id can be used only for photo
download and only for as long as the photo is not changed."""
big_file_unique_id: str
"""Unique file identifier of big (640x640) chat photo, which is supposed to be the same over
time and for different bots. Can't be used to download or reuse the file."""

View file

@ -16,7 +16,10 @@ class Document(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
thumb: Optional[PhotoSize] = None
"""Document thumbnail as defined by sender"""
file_name: Optional[str] = None

View file

@ -17,7 +17,10 @@ class File(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
file_size: Optional[int] = None
"""File size, if known"""
file_path: Optional[str] = None

View file

@ -17,10 +17,12 @@ class MessageEntity(TelegramObject):
"""
type: str
"""Type of the entity. Can be mention (@username), hashtag, cashtag, bot_command, url, email,
phone_number, bold (bold text), italic (italic text), code (monowidth string), pre
(monowidth block), text_link (for clickable text URLs), text_mention (for users without
usernames)"""
"""Type of the entity. Can be 'mention' (@username), 'hashtag' (#hashtag), 'cashtag' ($USD),
'bot_command' (/start@jobs_bot), 'url' (https://telegram.org), 'email'
(do-not-reply@telegram.org), 'phone_number' (+1-212-555-0123), 'bold' (bold text), 'italic'
(italic text), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'code'
(monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs),
'text_mention' (for users without usernames)"""
offset: int
"""Offset in UTF-16 code units to the start of the entity"""
length: int

View file

@ -12,7 +12,10 @@ class PassportFile(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
file_size: int
"""File size"""
file_date: int

View file

@ -13,7 +13,10 @@ class PhotoSize(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
width: int
"""Photo width"""
height: int

View file

@ -7,8 +7,8 @@ from pydantic import Field
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .user import User
from .shipping_address import ShippingAddress
from .user import User
class ShippingQuery(TelegramObject):

View file

@ -5,8 +5,8 @@ from typing import TYPE_CHECKING, Optional
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .photo_size import PhotoSize
from .mask_position import MaskPosition
from .photo_size import PhotoSize
class Sticker(TelegramObject):
@ -17,7 +17,10 @@ class Sticker(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
width: int
"""Sticker width"""
height: int

View file

@ -16,7 +16,10 @@ class Video(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
width: int
"""Video width as defined by sender"""
height: int

View file

@ -16,7 +16,10 @@ class VideoNote(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
length: int
"""Video width and height (diameter of the video message) as defined by sender"""
duration: int

View file

@ -13,7 +13,10 @@ class Voice(TelegramObject):
"""
file_id: str
"""Identifier for this file"""
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
duration: int
"""Duration of the audio in seconds as defined by sender"""
mime_type: Optional[str] = None

View file

@ -60,5 +60,5 @@ result: bool = await AddStickerToSet(...)
## Related pages:
- [Official documentation](https://core.telegram.org/bots/api#addstickertoset)
- [aiogram.types.InputFile](../types/input_file.md)
- [aiogram.types.MaskPosition](../types/mask_position.md)
- [aiogram.types.InputFile](../types/input_file.md)

View file

@ -62,5 +62,5 @@ result: bool = await CreateNewStickerSet(...)
## Related pages:
- [Official documentation](https://core.telegram.org/bots/api#createnewstickerset)
- [aiogram.types.InputFile](../types/input_file.md)
- [aiogram.types.MaskPosition](../types/mask_position.md)
- [aiogram.types.InputFile](../types/input_file.md)

View file

@ -9,7 +9,8 @@ This object represents an animation file (GIF or H.264/MPEG-4 AVC video without
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `width` | `#!python int` | Video width as defined by sender |
| `height` | `#!python int` | Video height as defined by sender |
| `duration` | `#!python int` | Duration of the video in seconds as defined by sender |

View file

@ -9,7 +9,8 @@ This object represents an audio file to be treated as music by the Telegram clie
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `duration` | `#!python int` | Duration of the audio in seconds as defined by sender |
| `performer` | `#!python Optional[str]` | Optional. Performer of the audio as defined by sender or by audio tags |
| `title` | `#!python Optional[str]` | Optional. Title of the audio as defined by sender or by audio tags |

View file

@ -30,5 +30,5 @@ NOTE: After the user presses a callback button, Telegram clients will display a
## Related pages:
- [Official documentation](https://core.telegram.org/bots/api#callbackquery)
- [aiogram.types.User](../types/user.md)
- [aiogram.types.Message](../types/message.md)
- [aiogram.types.User](../types/user.md)

View file

@ -20,6 +20,7 @@ This object represents a chat.
| `invite_link` | `#!python Optional[str]` | Optional. Chat invite link, for groups, supergroups and channel chats. Each administrator in a chat generates their own invite links, so the bot must first generate the link using exportChatInviteLink. Returned only in getChat. |
| `pinned_message` | `#!python Optional[Message]` | Optional. Pinned message, for groups, supergroups and channels. Returned only in getChat. |
| `permissions` | `#!python Optional[ChatPermissions]` | Optional. Default chat member permissions, for groups and supergroups. Returned only in getChat. |
| `slow_mode_delay` | `#!python Optional[int]` | Optional. For supergroups, the minimum allowed delay between consecutive messages sent by each unpriviledged user. Returned only in getChat. |
| `sticker_set_name` | `#!python Optional[str]` | Optional. For supergroups, name of group sticker set. Returned only in getChat. |
| `can_set_sticker_set` | `#!python Optional[bool]` | Optional. True, if the bot can change the group sticker set. Returned only in getChat. |

View file

@ -11,6 +11,7 @@ This object contains information about one member of a chat.
| - | - | - |
| `user` | `#!python User` | Information about the user |
| `status` | `#!python str` | The member's status in the chat. Can be 'creator', 'administrator', 'member', 'restricted', 'left' or 'kicked' |
| `custom_title` | `#!python Optional[str]` | Optional. Owner and administrators only. Custom title for this user |
| `until_date` | `#!python Optional[Union[int, datetime.datetime, datetime.timedelta]]` | Optional. Restricted and kicked only. Date when restrictions will be lifted for this user; unix time |
| `can_be_edited` | `#!python Optional[bool]` | Optional. Administrators only. True, if the bot is allowed to edit administrator privileges of that user |
| `can_post_messages` | `#!python Optional[bool]` | Optional. Administrators only. True, if the administrator can post in the channel; channels only |

View file

@ -10,7 +10,9 @@ This object represents a chat photo.
| Name | Type | Description |
| - | - | - |
| `small_file_id` | `#!python str` | File identifier of small (160x160) chat photo. This file_id can be used only for photo download and only for as long as the photo is not changed. |
| `small_file_unique_id` | `#!python str` | Unique file identifier of small (160x160) chat photo, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `big_file_id` | `#!python str` | File identifier of big (640x640) chat photo. This file_id can be used only for photo download and only for as long as the photo is not changed. |
| `big_file_unique_id` | `#!python str` | Unique file identifier of big (640x640) chat photo, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |

View file

@ -9,7 +9,8 @@ This object represents a general file (as opposed to photos, voice messages and
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `thumb` | `#!python Optional[PhotoSize]` | Optional. Document thumbnail as defined by sender |
| `file_name` | `#!python Optional[str]` | Optional. Original filename as defined by sender |
| `mime_type` | `#!python Optional[str]` | Optional. MIME type of the file as defined by sender |

View file

@ -11,7 +11,8 @@ Maximum file size to download is 20 MB
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `file_size` | `#!python Optional[int]` | Optional. File size, if known |
| `file_path` | `#!python Optional[str]` | Optional. File path. Use https://api.telegram.org/file/bot<token>/<file_path> to get the file. |

View file

@ -9,7 +9,7 @@ This object represents one special entity in a text message. For example, hashta
| Name | Type | Description |
| - | - | - |
| `type` | `#!python str` | Type of the entity. Can be mention (@username), hashtag, cashtag, bot_command, url, email, phone_number, bold (bold text), italic (italic text), code (monowidth string), pre (monowidth block), text_link (for clickable text URLs), text_mention (for users without usernames) |
| `type` | `#!python str` | Type of the entity. Can be 'mention' (@username), 'hashtag' (#hashtag), 'cashtag' ($USD), 'bot_command' (/start@jobs_bot), 'url' (https://telegram.org), 'email' (do-not-reply@telegram.org), 'phone_number' (+1-212-555-0123), 'bold' (bold text), 'italic' (italic text), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users without usernames) |
| `offset` | `#!python int` | Offset in UTF-16 code units to the start of the entity |
| `length` | `#!python int` | Length of the entity in UTF-16 code units |
| `url` | `#!python Optional[str]` | Optional. For 'text_link' only, url that will be opened after user taps on the text |

View file

@ -9,7 +9,8 @@ This object represents a file uploaded to Telegram Passport. Currently all Teleg
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `file_size` | `#!python int` | File size |
| `file_date` | `#!python int` | Unix time when the file was uploaded |

View file

@ -9,7 +9,8 @@ This object represents one size of a photo or a file / sticker thumbnail.
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `width` | `#!python int` | Photo width |
| `height` | `#!python int` | Photo height |
| `file_size` | `#!python Optional[int]` | Optional. File size |

View file

@ -9,7 +9,8 @@ This object represents a sticker.
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `width` | `#!python int` | Sticker width |
| `height` | `#!python int` | Sticker height |
| `is_animated` | `#!python bool` | True, if the sticker is animated |

View file

@ -9,7 +9,8 @@ This object represents a video file.
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `width` | `#!python int` | Video width as defined by sender |
| `height` | `#!python int` | Video height as defined by sender |
| `duration` | `#!python int` | Duration of the video in seconds as defined by sender |

View file

@ -9,7 +9,8 @@ This object represents a video message (available in Telegram apps as of v.4.0).
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `length` | `#!python int` | Video width and height (diameter of the video message) as defined by sender |
| `duration` | `#!python int` | Duration of the video in seconds as defined by sender |
| `thumb` | `#!python Optional[PhotoSize]` | Optional. Video thumbnail |

View file

@ -9,7 +9,8 @@ This object represents a voice note.
| Name | Type | Description |
| - | - | - |
| `file_id` | `#!python str` | Identifier for this file |
| `file_id` | `#!python str` | Identifier for this file, which can be used to download or reuse the file |
| `file_unique_id` | `#!python str` | Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. |
| `duration` | `#!python int` | Duration of the audio in seconds as defined by sender |
| `mime_type` | `#!python Optional[str]` | Optional. MIME type of the file as defined by sender |
| `file_size` | `#!python Optional[int]` | Optional. File size |

View file

@ -4,7 +4,7 @@ Documentation for version 3.0 [WIP] [^1]
[![MIT License](https://img.shields.io/pypi/l/aiogram.svg?style=flat-square)](https://opensource.org/licenses/MIT)
[![Supported python versions](https://img.shields.io/pypi/pyversions/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api)
[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-4.5-blue.svg?style=flat-square&logo=telegram)](https://core.telegram.org/bots/api)
[![PyPi Package Version](https://img.shields.io/pypi/v/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![PyPi status](https://img.shields.io/pypi/status/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
[![Downloads](https://img.shields.io/pypi/dm/aiogram.svg?style=flat-square)](https://pypi.python.org/pypi/aiogram)
@ -16,7 +16,7 @@ Documentation for version 3.0 [WIP] [^1]
## Features
- Asynchronous
- [Supports Telegram Bot API v4.4](api/index.md)
- [Supports Telegram Bot API v4.5](api/index.md)
- Finite State Machine
- [Replies into Webhook](https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates)
- Middlewares

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "aiogram"
version = "3.0.0-alpha.0"
version = "3.0.0-alpha.1"
description = "modern and fully asynchronous framework for Telegram Bot API"
authors = ["Alex Root Junior <jroot.junior@gmail.com>"]
license = "MIT"
@ -17,6 +17,7 @@ classifiers = [
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Libraries :: Application Frameworks",
]