Hydrogram is a Python MTProto client library for the Telegram API. It's an async-first framework supporting Python 3.9+ (CPython and PyPy), forked from Pyrogram with continued development.
Key Characteristics:
- License: LGPL-3.0
- Python: 3.9+ (supports CPython and PyPy)
- Architecture: Async/await-based with optional uvloop support
- Crypto: Uses pyaes (pure Python) or TgCrypto (C extension for performance)
hydrogram/
├── client.py # Main Client class (extends Methods)
├── methods/ # API method implementations
│ ├── __init__.py # Methods class (mixin of all method categories)
│ ├── auth/ # Authentication methods
│ ├── messages/ # Message operations
│ ├── chats/ # Chat/channel operations
│ ├── users/ # User operations
│ ├── bots/ # Bot-specific methods
│ ├── advanced/ # Advanced MTProto methods
│ ├── utilities/ # Utility methods (idle, compose, handlers)
│ └── pyromod/ # pyromod extensions (get_listener, etc.)
├── types/ # Telegram API type definitions
│ ├── object.py # Base Object class
│ ├── user_and_chats/ # User, Chat, etc.
│ ├── messages_and_media/ # Message, Photo, etc.
│ ├── input_media/ # Input media types
│ └── input_message_content/ # Input content types
├── filters.py # Message/update filters
├── handlers/ # Handler classes
├── dispatcher.py # Update dispatching
├── session/ # MTProto session management
│ ├── session.py
│ └── internals/
├── connection/ # Network connection layer
│ ├── connection.py
│ └── transport/tcp/ # TCP transports (abridged, intermediate, full)
├── storage/ # Session storage backends
│ ├── base.py # Abstract base
│ └── sqlite_storage.py
├── crypto/ # Cryptographic operations
├── raw/ # Raw MTProto layer (auto-generated)
├── enums/ # Enumeration definitions
├── errors/ # Exception classes
├── parser/ # HTML/Markdown parsers
└── helpers/ # Utility helpers
- Ruff is used for both linting and formatting (line length: 99)
- Pre-commit hooks enforce style
- Run
ruff check .before committing
FURB- refurb suggestionsI- isort import sortingE,W- pycodestyleUP- pyupgradeSIM- simplificationPERF- performance lintsN- PEP8 namingFA- future annotations- Type-checking imports in
TYPE_CHECKINGblocks
1. License Header All new Python files must include the following LGPL header:
# Hydrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2023-present Hydrogram <https://hydrogram.org>
#
# This file is part of Hydrogram.
#
# Hydrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Hydrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Hydrogram. If not, see <http://www.gnu.org/licenses/>.Note: Existing files retain both copyright holders (Dan + Hydrogram). New files should ONLY have the Hydrogram copyright line.
2. Future Annotations
Always use from __future__ import annotations for forward references.
3. Type Hinting
- Full type hints on all public APIs
- Use
TYPE_CHECKINGblocks for circular imports - Prefer
str | NoneoverOptional[str](Python 3.10+ style)
# methods/__init__.py
class Methods(
Advanced, Auth, Bots, Contacts, Password, Chats,
Users, Messages, Pyromod, Decorators, Utilities, InviteLinks, Phone
):
pass
# client.py
class Client(Methods):
"""Main client class, inherits all methods"""All Telegram types inherit from Object:
class Object:
def __init__(self, client: "hydrogram.Client" = None):
self._client = client
def bind(self, client: "hydrogram.Client"):
"""Bind client to this and nested objects"""
def __str__(self) -> str: # JSON serialization
def __repr__(self) -> str: # Python repr
def __eq__(self, other) -> bool: # Deep equalityfrom __future__ import annotations
from typing import TYPE_CHECKING
import hydrogram
from hydrogram import enums, raw, types, utils
if TYPE_CHECKING:
from datetime import datetime
class SendMessage: # Class name in PascalCase matching method name
async def send_message(
self: hydrogram.Client,
chat_id: int | str,
text: str,
*, # Force keyword-only arguments after required params
message_thread_id: int | None = None,
parse_mode: enums.ParseMode | None = None,
entities: list[types.MessageEntity] | None = None,
disable_web_page_preview: bool | None = None,
disable_notification: bool | None = None,
reply_to_message_id: int | None = None,
schedule_date: datetime | None = None,
protect_content: bool | None = None,
reply_markup: types.InlineKeyboardMarkup
| types.ReplyKeyboardMarkup
| types.ReplyKeyboardRemove
| types.ForceReply = None,
) -> types.Message:
"""Send text messages.
.. include:: /_includes/usable-by/users-bots.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
text (``str``):
Text of the message to be sent.
message_thread_id (``int``, *optional*):
Unique identifier for the target message thread (topic) of the forum.
for forum supergroups only.
parse_mode (:obj:`~hydrogram.enums.ParseMode`, *optional*):
By default, texts are parsed using both Markdown and HTML styles.
You can combine both syntaxes together.
entities (List of :obj:`~hydrogram.types.MessageEntity`):
List of special entities that appear in message text, which can be specified instead of *parse_mode*.
disable_web_page_preview (``bool``, *optional*):
Disables link previews for links in this message.
disable_notification (``bool``, *optional*):
Sends the message silently.
Users will receive a notification with no sound.
Returns:
:obj:`~hydrogram.types.Message`: On success, the sent message is returned.
Example:
.. code-block:: python
# Simple send
await app.send_message(chat_id, "Hello")
# With formatting
await app.send_message(chat_id, "**Bold** text", parse_mode=enums.ParseMode.MARKDOWN)
"""
# Method implementation...CRITICAL: After creating the method file, you MUST add it to the category mixin class:
# File: hydrogram/methods/messages/__init__.py
from .send_message import SendMessage # Import the new method class
# ... other imports ...
class Messages(
# ... other method classes ...
SendMessage, # Add the class here (inherits the method)
):
passThe mixin class inherits from all individual method classes, making their methods available on the Client via multiple inheritance.
Filters are callable classes:
class Filter:
async def __call__(self, client: hydrogram.Client, update: Update) -> bool:
raise NotImplementedError
# Support logical operators
def __invert__(self) -> InvertFilter
def __and__(self, other: Filter) -> AndFilter
def __or__(self, other: Filter) -> OrFilterBuilt-in filters: filters.command, filters.private, filters.chat, etc.
Handlers wrap callback functions:
from __future__ import annotations
import inspect
from typing import TYPE_CHECKING, Callable
if TYPE_CHECKING:
import hydrogram
from hydrogram.filters import Filter
from hydrogram.types import Update
class Handler:
def __init__(self, callback: Callable, filters: Filter | None = None):
self.callback = callback
self.filters = filters
async def check(self, client: hydrogram.Client, update: Update):
if callable(self.filters):
if inspect.iscoroutinefunction(self.filters.__call__):
return await self.filters(client, update)
return await client.loop.run_in_executor(client.executor, self.filters, client, update)
return TrueSpecialized handlers inherit from Handler:
MessageHandlerCallbackQueryHandlerInlineQueryHandler- etc.
Storage backends implement BaseStorage:
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Union
from hydrogram import raw
InputPeer = Union[raw.types.InputPeerUser, raw.types.InputPeerChat, raw.types.InputPeerChannel]
class BaseStorage(ABC):
"""Abstract base class for storage engines."""
SESSION_STRING_FORMAT: str = ">BI?256sQ?"
def __init__(self, name: str) -> None:
self.name = name
@abstractmethod
async def open(self) -> None: ...
@abstractmethod
async def save(self) -> None: ...
@abstractmethod
async def close(self) -> None: ...
@abstractmethod
async def dc_id(self, value: int | None = None) -> int: ...
@abstractmethod
async def api_id(self, value: int | None = None) -> int: ...
@abstractmethod
async def auth_key(self, value: bytes | None = None) -> bytes: ...
@abstractmethod
async def update_peers(self, peers: list[tuple[int, int, str, str, str]]) -> None: ...
@abstractmethod
async def get_peer_by_id(self, peer_id: int) -> InputPeer: ...# Custom exceptions for RPC errors
class SomeTelegramError(Exception):
"""Description of when this occurs"""Tests use pytest and follow this pattern:
# Hydrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2023-present Hydrogram <https://hydrogram.org>
#
# This file is part of Hydrogram.
# ... (license header)
import pytest
from hydrogram.module import ThingToTest
def test_something():
result = ThingToTest.method()
assert result == expected
async def test_async_something():
result = await ThingToTest.async_method()
assert result == expectedpytest tests/ # Run all tests
pytest tests/test_file.py::test_func # Run specific test
pytest --cov=hydrogram # With coverage- Use
uvfor dependency management:uv sync --all-extras - Dev dependencies: ruff, pytest, pre-commit, httpx, lxml, hatchling
- Optional: tgcrypto, uvloop (for performance)
pytest tests/ # Run all tests
pytest tests/test_file.py::test_func # Run specific test
pytest --cov=hydrogram # With coveragepre-commit install # Install hooks
pre-commit run --all-files # Run manuallyThe compiler generates raw MTProto layer from TL schema:
compiler/api/- API schema compilercompiler/errors/- Error compilercompiler/docs/- Documentation compiler
Run via hatch build hooks.
- Create method file in appropriate category (e.g.,
methods/messages/send_sticker.py) - Follow signature pattern with type hints (see Method Implementation Pattern above)
- Include docstring with Parameters/Returns sections
- IMPORTANT: Import and add to category mixin class in
methods/<category>/__init__.py - Write test in
tests/ - Add news fragment:
news/<issue>.feature.rst
- Create in appropriate
types/subpackage - Inherit from
Object - Define
__init__with all fields - Add to subpackage
__init__.py - Add to main
types/__init__.py
- Add to
filters.py - Create filter class inheriting from
Filteror use filter decorator - Export in
filtersmodule
- Async Context: All API calls are async - never use blocking I/O
- Client Binding: Objects must be bound to a client to use bound methods
- Raw Layer: Use
rawmodule for low-level MTProto if high-level API lacks something - Session Storage: SQLite is default; storage is pluggable via
BaseStorage - Crypto Executor: CPU-intensive crypto runs in
ThreadPoolExecutor(1)
- Sphinx docs in
docs/ - Live preview:
sphinx-autobuild docs/source/ docs/build/ --watch hydrogram/ - Follow Google docstring style
- Reference external Telegram docs where applicable
- Uses
towncrierfor changelog management - Fragments in
news/directory:.feature.rst- new features.bugfix.rst- bug fixes.doc.rst- documentation.removal.rst- deprecations.misc.rst- other changes
- Version defined in
hydrogram/__init__.py:__version__
| File | Purpose |
|---|---|
pyproject.toml |
Project config, dependencies, hatch build |
ruff.toml |
Linting/formatting rules |
hatch_build.py |
Custom build hooks (generates raw layer) |
.pre-commit-config.yaml |
Pre-commit hooks |
compiler/api/source/main_api.tl |
TL schema source |
hydrogram/__init__.py |
Public API exports |
- Docs: https://docs.hydrogram.org
- Homepage: https://hydrogram.org
- Telegram: https://t.me/HydrogramNews
- Issues: https://github.com/hydrogram/hydrogram/issues
- Based on: https://github.com/pyrogram/pyrogram