Skip to content
This repository was archived by the owner on Feb 11, 2026. It is now read-only.
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
153 changes: 153 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Tests

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13']

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install runtime + test tools
run: |
python -m pip install --upgrade pip
# Устанавливаем package (runtime deps)
pip install -e "."
# Явно ставим инструменты для тестов/линта, чтобы они были доступны в runner
pip install pytest pytest-asyncio pytest-cov pytest-timeout flake8 mypy

- name: Lint with flake8
run: |
flake8 src/pymax tests \
--count \
--select=E9,F63,F7,F82 \
--show-source \
--statistics
flake8 src/pymax tests \
--count \
--exit-zero \
--max-complexity=10 \
--max-line-length=79 \
--statistics
continue-on-error: true

- name: Type check with mypy
run: |
mypy src/pymax \
--ignore-missing-imports \
--no-error-summary
continue-on-error: true

- name: Run unit tests
run: |
pytest -m "not mockserver" \
--cov=src/pymax \
--cov-report=xml \
--cov-report=term-missing

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false

- name: Archive pytest cache
if: always()
uses: actions/upload-artifact@v4
with:
name: pytest-cache-${{ matrix.python-version }}
path: .pytest_cache/
retention-days: 5

integration-tests:
runs-on: ubuntu-latest

steps:
- name: Checkout repository (with submodules)
uses: actions/checkout@v4
with:
submodules: recursive

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install runtime + test tools (integration)
run: |
python -m pip install --upgrade pip
pip install -e "."
pip install pytest pytest-asyncio pytest-cov pytest-timeout flake8 mypy

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Start MockServer
run: |
git clone https://github.com/fresh-milkshake/gomax-prerelease.git
cd gomax-prerelease/mockserver
go mod download
go run cmd/server/main.go &
sleep 3
Comment on lines +112 to +118
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unpinned external repository and fragile server startup.

Two concerns:

  1. Unpinned repo: Cloning gomax-prerelease without a specific commit/tag means CI can break unexpectedly if that repo changes.
  2. Hardcoded sleep: sleep 3 is fragile; the server may not be ready in time, causing flaky tests.
🔎 Proposed improvements
       - name: Start MockServer
         run: |
-          git clone https://github.com/fresh-milkshake/gomax-prerelease.git
+          git clone --depth 1 --branch v1.0.0 https://github.com/fresh-milkshake/gomax-prerelease.git
           cd gomax-prerelease/mockserver
           go mod download
           go run cmd/server/main.go &
-          sleep 3
+          # Wait for server to be ready
+          for i in {1..30}; do
+            curl -s http://localhost:8080/health && break || sleep 1
+          done
🤖 Prompt for AI Agents
.github/workflows/tests.yml around lines 108 to 114: the workflow clones an
unpinned external repo and uses a fragile fixed sleep to wait for the mock
server; update the clone to fetch a specific tag or commit (or add the repo as a
submodule) to pin the version, and replace the hardcoded sleep with a readiness
loop that waits for the server to respond (e.g., poll an HTTP health endpoint
with a timeout and exit non‑zero on failure) so CI is deterministic and not
flaky.


- name: Run integration tests
run: |
pytest -m mockserver -v --tb=short
continue-on-error: true
env:
MOCKSERVER_WS_URL: ws://localhost:8080/
MOCKSERVER_HTTP_URL: http://localhost:8080

code-quality:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install dependencies + quality tools
run: |
python -m pip install --upgrade pip
pip install -e "."
# black/isort/pylint используются только в этом job
pip install black isort pylint

- name: Check code formatting with black
run: black --check src/pymax tests
continue-on-error: true

- name: Check import sorting with isort
run: isort --check-only src/pymax tests
continue-on-error: true
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,41 @@ uv add -U maxapi-python

## Быстрый старт

### Аутентификация (`device_type`)

> [!IMPORTANT]
> Параметр `device_type` в `UserAgentPayload` **критически важен** для выбора способа авторизации:

**Вход по номеру телефона (DESKTOP):**

```python
from pymax import MaxClient
from pymax.payloads import UserAgentPayload

ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")

client = MaxClient(
phone="+79111111111",
work_dir="cache",
headers=ua,
)
```

**Вход через QR-код (WEB)** — токен совместим с веб-версией Max:

```python
from pymax import MaxClient
from pymax.payloads import UserAgentPayload

ua = UserAgentPayload(device_type="WEB", app_version="25.12.13")

client = MaxClient(
phone="+7911111111",
work_dir="cache",
headers=ua,
)
```

### Базовый пример использования

```python
Expand Down
4 changes: 2 additions & 2 deletions examples/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from pymax import MaxClient
from pymax.payloads import UserAgentPayload

ua = UserAgentPayload(device_type="WEB")
ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")

client = MaxClient(
phone="+79911111111",
phone="+79116290861",
work_dir="cache",
headers=ua,
)
Expand Down
54 changes: 53 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "maxapi-python"
version = "1.2.1"
version = "1.2.2"
description = "Python wrapper для API мессенджера Max"
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -36,7 +36,25 @@ where = ["src"]
[tool.setuptools.package-dir]
"" = "src"

[project.optional-dependencies]
test = [
"pytest>=8.0.0",
"pytest-asyncio>=0.24.0",
"pytest-cov>=5.0.0",
"pytest-timeout>=2.1.0",
"flake8",
"mypy",
]

[dependency-groups]
test = [
"pytest>=8.0.0",
"pytest-asyncio>=0.24.0",
"pytest-cov>=5.0.0",
"pytest-timeout>=2.1.0",
"flake8",
"mypy",
]
dev = [
"furo>=2025.9.25",
"ghp-import>=2.1.0",
Expand All @@ -46,6 +64,10 @@ dev = [
"pre-commit>=4.3.0",
"pydocstring>=0.2.1",
"sphinx>=8.1.3",
"pytest>=8.0.0",
"pytest-asyncio>=0.24.0",
"pytest-cov>=5.0.0",
"pytest-timeout>=2.1.0",
]

[tool.hatch.build.targets.wheel]
Expand Down Expand Up @@ -74,3 +96,33 @@ profile = "black"
line_length = 79
multi_line_output = 3
include_trailing_comma = true

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = "-v --tb=short --strict-markers"
markers = [
"asyncio: marker for asyncio tests",
"mockserver: marker for MockServer integration tests",
"integration: marker for integration tests",
"slow: marker for slow tests",
]

[tool.coverage.run]
source = ["src/pymax"]
branch = true

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
22 changes: 21 additions & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
[pytest]

asyncio_mode = auto

testpaths = tests

python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short

addopts =
-v
--tb=short
--strict-markers
-ra
--color=yes

markers =
asyncio: асинхронные тесты
mockserver: интеграционные тесты с MockServer
integration: интеграционные тесты
slow: медленные тесты
unit: модульные тесты
skip_ci: пропустить в CI

timeout = 30
22 changes: 22 additions & 0 deletions redocs/source/clients.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ MaxClient
logger=None, # Пользовательский логгер
)

.. warning::

Параметр ``device_type`` в ``UserAgentPayload`` **критически важен** для выбора способа авторизации:

**DESKTOP** — вход по номеру телефона:

.. code-block:: python

from pymax.payloads import UserAgentPayload

ua = UserAgentPayload(device_type="DESKTOP", app_version="25.12.13")
client = MaxClient(phone="+79111111111", headers=ua)

**WEB** — вход через QR-код; токен совместим с веб-версией Max:

.. code-block:: python

from pymax.payloads import UserAgentPayload

ua = UserAgentPayload(device_type="WEB", app_version="25.12.13")
client = MaxClient(phone="+79111111111", headers=ua)

Основные методы:

.. code-block:: python
Expand Down
2 changes: 1 addition & 1 deletion src/pymax/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ async def start(self) -> None:
if self._token is None:
await self._login()

await self._sync()
await self._sync(self.user_agent)

await self._post_login_tasks(sync=False)

Expand Down
Loading
Loading