checkmax-phone-utils 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checkmax_phone_utils-0.2.0/.github/workflows/ci.yml +35 -0
- checkmax_phone_utils-0.2.0/.gitignore +99 -0
- checkmax_phone_utils-0.2.0/LICENSE +21 -0
- checkmax_phone_utils-0.2.0/PKG-INFO +174 -0
- checkmax_phone_utils-0.2.0/README.md +142 -0
- checkmax_phone_utils-0.2.0/checkmax_phone_utils/__init__.py +56 -0
- checkmax_phone_utils-0.2.0/checkmax_phone_utils/client.py +146 -0
- checkmax_phone_utils-0.2.0/checkmax_phone_utils/exceptions.py +65 -0
- checkmax_phone_utils-0.2.0/checkmax_phone_utils/normalizer.py +95 -0
- checkmax_phone_utils-0.2.0/checkmax_phone_utils/validator.py +124 -0
- checkmax_phone_utils-0.2.0/examples/api_client_demo.py +49 -0
- checkmax_phone_utils-0.2.0/examples/basic_validation.py +35 -0
- checkmax_phone_utils-0.2.0/examples/bulk_normalize.py +70 -0
- checkmax_phone_utils-0.2.0/openapi/checkmax-api.openapi.yaml +268 -0
- checkmax_phone_utils-0.2.0/pyproject.toml +62 -0
- checkmax_phone_utils-0.2.0/tests/__init__.py +0 -0
- checkmax_phone_utils-0.2.0/tests/test_client.py +102 -0
- checkmax_phone_utils-0.2.0/tests/test_normalizer.py +77 -0
- checkmax_phone_utils-0.2.0/tests/test_validator.py +108 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: pytest (Python ${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
cache: pip
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: |
|
|
31
|
+
python -m pip install --upgrade pip
|
|
32
|
+
pip install -e ".[dev]"
|
|
33
|
+
|
|
34
|
+
- name: Run pytest
|
|
35
|
+
run: pytest -ra
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
htmlcov/
|
|
39
|
+
.tox/
|
|
40
|
+
.nox/
|
|
41
|
+
.coverage
|
|
42
|
+
.coverage.*
|
|
43
|
+
.cache
|
|
44
|
+
nosetests.xml
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
*.py,cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
cover/
|
|
51
|
+
|
|
52
|
+
# Translations
|
|
53
|
+
*.mo
|
|
54
|
+
*.pot
|
|
55
|
+
|
|
56
|
+
# Jupyter Notebook
|
|
57
|
+
.ipynb_checkpoints
|
|
58
|
+
|
|
59
|
+
# IPython
|
|
60
|
+
profile_default/
|
|
61
|
+
ipython_config.py
|
|
62
|
+
|
|
63
|
+
# pyenv
|
|
64
|
+
.python-version
|
|
65
|
+
|
|
66
|
+
# pipenv / Poetry / PDM
|
|
67
|
+
Pipfile.lock
|
|
68
|
+
poetry.lock
|
|
69
|
+
pdm.lock
|
|
70
|
+
__pypackages__/
|
|
71
|
+
|
|
72
|
+
# Environments
|
|
73
|
+
.env
|
|
74
|
+
.env.*
|
|
75
|
+
.venv
|
|
76
|
+
env/
|
|
77
|
+
venv/
|
|
78
|
+
ENV/
|
|
79
|
+
env.bak/
|
|
80
|
+
venv.bak/
|
|
81
|
+
|
|
82
|
+
# mypy / pytype / ruff
|
|
83
|
+
.mypy_cache/
|
|
84
|
+
.dmypy.json
|
|
85
|
+
dmypy.json
|
|
86
|
+
.pytype/
|
|
87
|
+
.ruff_cache/
|
|
88
|
+
|
|
89
|
+
# IDE / editor
|
|
90
|
+
.idea/
|
|
91
|
+
.vscode/
|
|
92
|
+
*.swp
|
|
93
|
+
*.swo
|
|
94
|
+
*~
|
|
95
|
+
|
|
96
|
+
# OS
|
|
97
|
+
.DS_Store
|
|
98
|
+
Thumbs.db
|
|
99
|
+
desktop.ini
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CheckMax Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: checkmax-phone-utils
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Phone validation utilities (E.164 + libphonenumber) and the official REST client for the CheckMaxApp phone-verification API.
|
|
5
|
+
Project-URL: Homepage, https://checkmaxapp.com
|
|
6
|
+
Project-URL: Documentation, https://checkmaxapp.com/api
|
|
7
|
+
Project-URL: Repository, https://github.com/abragimbaliev/checkmax-phone-utils
|
|
8
|
+
Project-URL: Issues, https://github.com/abragimbaliev/checkmax-phone-utils/issues
|
|
9
|
+
Author-email: CheckMax Team <dev@checkmaxapp.com>
|
|
10
|
+
Maintainer-email: CheckMax Team <dev@checkmaxapp.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: checkmaxapp,e164,libphonenumber,max-messenger,phone,validation
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Communications :: Telephony
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: phonenumbers>=8.13
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# checkmax-phone-utils
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/checkmax-phone-utils/)
|
|
36
|
+
[](https://www.python.org/downloads/)
|
|
37
|
+
[](LICENSE)
|
|
38
|
+
[](https://checkmaxapp.com)
|
|
39
|
+
|
|
40
|
+
A small, focused Python toolkit for phone-number **format validation**,
|
|
41
|
+
**E.164 normalization**, and an **optional API client** for the
|
|
42
|
+
[CheckMaxApp](https://checkmaxapp.com) service. Built on top of Google's
|
|
43
|
+
[libphonenumber](https://github.com/google/libphonenumber) (Python port).
|
|
44
|
+
Use it as a drop-in helper in your own code, or as the official client
|
|
45
|
+
library for the live CheckMaxApp REST API.
|
|
46
|
+
|
|
47
|
+
> **Powered by [CheckMaxApp](https://checkmaxapp.com) — phone validation
|
|
48
|
+
> service for the MAX messenger.** This library handles *format* and
|
|
49
|
+
> *region* validation. To check whether a number is actually registered
|
|
50
|
+
> on MAX, use the hosted CheckMaxApp service.
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install checkmax-phone-utils
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from checkmax_phone_utils import normalize, validate_e164, is_mobile
|
|
60
|
+
|
|
61
|
+
normalize("+7 (916) 123-45-67") # '+79161234567'
|
|
62
|
+
normalize("8 916 123-45-67", "RU") # '+79161234567'
|
|
63
|
+
validate_e164("+1 415 555 2671") # (True, '+14155552671')
|
|
64
|
+
is_mobile("+79161234567") # True
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## What it does
|
|
68
|
+
|
|
69
|
+
| Function | Purpose |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `normalize(raw, default_region)` | Convert any phone-shaped input to canonical E.164, or `None`. |
|
|
72
|
+
| `validate_e164(raw, region)` | Return `(is_valid, e164_or_none)` for a raw input. |
|
|
73
|
+
| `is_mobile(e164)` | True if the number is mobile (or mobile-or-fixed). |
|
|
74
|
+
| `detect_region(raw)` | Return ISO 3166-1 alpha-2 region of an international number. |
|
|
75
|
+
| `clean(raw)` | Strip everything that is not a digit or leading `+`. |
|
|
76
|
+
| `CheckMaxClient(api_key).check([...])` | Verify numbers via the REST API — registration status + public name. |
|
|
77
|
+
|
|
78
|
+
Full reference: see the docstrings — every public function has examples
|
|
79
|
+
and edge-case notes.
|
|
80
|
+
|
|
81
|
+
## API reference
|
|
82
|
+
|
|
83
|
+
### `normalize(raw: str, default_region: str = "RU") -> str | None`
|
|
84
|
+
|
|
85
|
+
Safe high-level entry point. Cleans, parses, validates, and returns the
|
|
86
|
+
canonical `+CCNNNNNNNNNNN` form, or `None` if the input is not a valid
|
|
87
|
+
phone number.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
>>> normalize("+7 (916) 123-45-67")
|
|
91
|
+
'+79161234567'
|
|
92
|
+
>>> normalize("garbage") is None
|
|
93
|
+
True
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `validate_e164(raw: str, region: str = "RU") -> tuple[bool, str | None]`
|
|
97
|
+
|
|
98
|
+
Returns `(is_valid, e164)`. When valid, `e164` is the canonical form;
|
|
99
|
+
otherwise it is `None`. Useful when you want to keep both signals.
|
|
100
|
+
|
|
101
|
+
### `is_mobile(e164: str) -> bool`
|
|
102
|
+
|
|
103
|
+
True for `MOBILE` and `FIXED_LINE_OR_MOBILE` numbers; False otherwise,
|
|
104
|
+
including when the input cannot be parsed.
|
|
105
|
+
|
|
106
|
+
### `detect_region(raw: str) -> str | None`
|
|
107
|
+
|
|
108
|
+
Detect the ISO region from an international-format number (with `+` or
|
|
109
|
+
`00` prefix). Returns `None` when the input lacks a country prefix.
|
|
110
|
+
|
|
111
|
+
### `CheckMaxClient(api_key: str)`
|
|
112
|
+
|
|
113
|
+
Official client for the [CheckMaxApp REST API](https://checkmaxapp.com/api).
|
|
114
|
+
Get an API key from the CheckMaxApp Telegram bot. Methods: `health()`,
|
|
115
|
+
`balance()`, `usage()`, `check(phones)`, `batch_create(phones)`,
|
|
116
|
+
`batch_status(id)`, `batch_download(id)`. Raises `AuthError` (401) and
|
|
117
|
+
`InsufficientBalanceError` (402).
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from checkmax_phone_utils import CheckMaxClient
|
|
121
|
+
|
|
122
|
+
client = CheckMaxClient(api_key="mxk_...")
|
|
123
|
+
client.check(["79001234567"])
|
|
124
|
+
# [{'phone': '79001234567', 'status': 'registered',
|
|
125
|
+
# 'first_name': 'Ivan', 'last_name': 'Petrov'}]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The full machine-readable schema lives at
|
|
129
|
+
[`openapi/checkmax-api.openapi.yaml`](openapi/checkmax-api.openapi.yaml)
|
|
130
|
+
(OpenAPI 3.0) — also published to the API directories.
|
|
131
|
+
|
|
132
|
+
## Examples
|
|
133
|
+
|
|
134
|
+
The [`examples/`](examples) directory contains runnable scripts:
|
|
135
|
+
|
|
136
|
+
- `examples/basic_validation.py` — single-number validation against a
|
|
137
|
+
small sample list.
|
|
138
|
+
- `examples/bulk_normalize.py` — normalize a CSV file of phones in
|
|
139
|
+
bulk, emitting a CSV with `e164` and `valid` columns.
|
|
140
|
+
- `examples/api_client_demo.py` — intended surface of the
|
|
141
|
+
CheckMaxApp client.
|
|
142
|
+
|
|
143
|
+
## Roadmap
|
|
144
|
+
|
|
145
|
+
- v0.1 — format validation, E.164 normalization, stub client.
|
|
146
|
+
- v0.2 — live REST client (check / batch / balance / usage), structured
|
|
147
|
+
errors, OpenAPI 3.0 spec (current).
|
|
148
|
+
- v0.3 — async client, retries, optional caching.
|
|
149
|
+
- v0.4 — type stubs on PyPI, CLI entry point (`checkmax phone <num>`).
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
git clone https://github.com/abragimbaliev/checkmax-phone-utils.git
|
|
155
|
+
cd checkmax-phone-utils
|
|
156
|
+
pip install -e ".[dev]"
|
|
157
|
+
pytest
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
CI runs `pytest` against Python 3.10, 3.11, and 3.12 on every push.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
[MIT](LICENSE). Copyright (c) 2026 CheckMax Team.
|
|
165
|
+
|
|
166
|
+
## Authors
|
|
167
|
+
|
|
168
|
+
CheckMax Team — `dev@checkmaxapp.com`
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
Looking for the hosted service?
|
|
173
|
+
**[CheckMaxApp](https://checkmaxapp.com)** — phone validation for the
|
|
174
|
+
MAX messenger.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# checkmax-phone-utils
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/checkmax-phone-utils/)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://checkmaxapp.com)
|
|
7
|
+
|
|
8
|
+
A small, focused Python toolkit for phone-number **format validation**,
|
|
9
|
+
**E.164 normalization**, and an **optional API client** for the
|
|
10
|
+
[CheckMaxApp](https://checkmaxapp.com) service. Built on top of Google's
|
|
11
|
+
[libphonenumber](https://github.com/google/libphonenumber) (Python port).
|
|
12
|
+
Use it as a drop-in helper in your own code, or as the official client
|
|
13
|
+
library for the live CheckMaxApp REST API.
|
|
14
|
+
|
|
15
|
+
> **Powered by [CheckMaxApp](https://checkmaxapp.com) — phone validation
|
|
16
|
+
> service for the MAX messenger.** This library handles *format* and
|
|
17
|
+
> *region* validation. To check whether a number is actually registered
|
|
18
|
+
> on MAX, use the hosted CheckMaxApp service.
|
|
19
|
+
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install checkmax-phone-utils
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from checkmax_phone_utils import normalize, validate_e164, is_mobile
|
|
28
|
+
|
|
29
|
+
normalize("+7 (916) 123-45-67") # '+79161234567'
|
|
30
|
+
normalize("8 916 123-45-67", "RU") # '+79161234567'
|
|
31
|
+
validate_e164("+1 415 555 2671") # (True, '+14155552671')
|
|
32
|
+
is_mobile("+79161234567") # True
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## What it does
|
|
36
|
+
|
|
37
|
+
| Function | Purpose |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `normalize(raw, default_region)` | Convert any phone-shaped input to canonical E.164, or `None`. |
|
|
40
|
+
| `validate_e164(raw, region)` | Return `(is_valid, e164_or_none)` for a raw input. |
|
|
41
|
+
| `is_mobile(e164)` | True if the number is mobile (or mobile-or-fixed). |
|
|
42
|
+
| `detect_region(raw)` | Return ISO 3166-1 alpha-2 region of an international number. |
|
|
43
|
+
| `clean(raw)` | Strip everything that is not a digit or leading `+`. |
|
|
44
|
+
| `CheckMaxClient(api_key).check([...])` | Verify numbers via the REST API — registration status + public name. |
|
|
45
|
+
|
|
46
|
+
Full reference: see the docstrings — every public function has examples
|
|
47
|
+
and edge-case notes.
|
|
48
|
+
|
|
49
|
+
## API reference
|
|
50
|
+
|
|
51
|
+
### `normalize(raw: str, default_region: str = "RU") -> str | None`
|
|
52
|
+
|
|
53
|
+
Safe high-level entry point. Cleans, parses, validates, and returns the
|
|
54
|
+
canonical `+CCNNNNNNNNNNN` form, or `None` if the input is not a valid
|
|
55
|
+
phone number.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
>>> normalize("+7 (916) 123-45-67")
|
|
59
|
+
'+79161234567'
|
|
60
|
+
>>> normalize("garbage") is None
|
|
61
|
+
True
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `validate_e164(raw: str, region: str = "RU") -> tuple[bool, str | None]`
|
|
65
|
+
|
|
66
|
+
Returns `(is_valid, e164)`. When valid, `e164` is the canonical form;
|
|
67
|
+
otherwise it is `None`. Useful when you want to keep both signals.
|
|
68
|
+
|
|
69
|
+
### `is_mobile(e164: str) -> bool`
|
|
70
|
+
|
|
71
|
+
True for `MOBILE` and `FIXED_LINE_OR_MOBILE` numbers; False otherwise,
|
|
72
|
+
including when the input cannot be parsed.
|
|
73
|
+
|
|
74
|
+
### `detect_region(raw: str) -> str | None`
|
|
75
|
+
|
|
76
|
+
Detect the ISO region from an international-format number (with `+` or
|
|
77
|
+
`00` prefix). Returns `None` when the input lacks a country prefix.
|
|
78
|
+
|
|
79
|
+
### `CheckMaxClient(api_key: str)`
|
|
80
|
+
|
|
81
|
+
Official client for the [CheckMaxApp REST API](https://checkmaxapp.com/api).
|
|
82
|
+
Get an API key from the CheckMaxApp Telegram bot. Methods: `health()`,
|
|
83
|
+
`balance()`, `usage()`, `check(phones)`, `batch_create(phones)`,
|
|
84
|
+
`batch_status(id)`, `batch_download(id)`. Raises `AuthError` (401) and
|
|
85
|
+
`InsufficientBalanceError` (402).
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from checkmax_phone_utils import CheckMaxClient
|
|
89
|
+
|
|
90
|
+
client = CheckMaxClient(api_key="mxk_...")
|
|
91
|
+
client.check(["79001234567"])
|
|
92
|
+
# [{'phone': '79001234567', 'status': 'registered',
|
|
93
|
+
# 'first_name': 'Ivan', 'last_name': 'Petrov'}]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The full machine-readable schema lives at
|
|
97
|
+
[`openapi/checkmax-api.openapi.yaml`](openapi/checkmax-api.openapi.yaml)
|
|
98
|
+
(OpenAPI 3.0) — also published to the API directories.
|
|
99
|
+
|
|
100
|
+
## Examples
|
|
101
|
+
|
|
102
|
+
The [`examples/`](examples) directory contains runnable scripts:
|
|
103
|
+
|
|
104
|
+
- `examples/basic_validation.py` — single-number validation against a
|
|
105
|
+
small sample list.
|
|
106
|
+
- `examples/bulk_normalize.py` — normalize a CSV file of phones in
|
|
107
|
+
bulk, emitting a CSV with `e164` and `valid` columns.
|
|
108
|
+
- `examples/api_client_demo.py` — intended surface of the
|
|
109
|
+
CheckMaxApp client.
|
|
110
|
+
|
|
111
|
+
## Roadmap
|
|
112
|
+
|
|
113
|
+
- v0.1 — format validation, E.164 normalization, stub client.
|
|
114
|
+
- v0.2 — live REST client (check / batch / balance / usage), structured
|
|
115
|
+
errors, OpenAPI 3.0 spec (current).
|
|
116
|
+
- v0.3 — async client, retries, optional caching.
|
|
117
|
+
- v0.4 — type stubs on PyPI, CLI entry point (`checkmax phone <num>`).
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git clone https://github.com/abragimbaliev/checkmax-phone-utils.git
|
|
123
|
+
cd checkmax-phone-utils
|
|
124
|
+
pip install -e ".[dev]"
|
|
125
|
+
pytest
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
CI runs `pytest` against Python 3.10, 3.11, and 3.12 on every push.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
[MIT](LICENSE). Copyright (c) 2026 CheckMax Team.
|
|
133
|
+
|
|
134
|
+
## Authors
|
|
135
|
+
|
|
136
|
+
CheckMax Team — `dev@checkmaxapp.com`
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
Looking for the hosted service?
|
|
141
|
+
**[CheckMaxApp](https://checkmaxapp.com)** — phone validation for the
|
|
142
|
+
MAX messenger.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""checkmax-phone-utils — Phone validation utilities + CheckMaxApp API client.
|
|
2
|
+
|
|
3
|
+
Companion library to CheckMaxApp (https://checkmaxapp.com), a phone
|
|
4
|
+
validation service for the MAX messenger. Format-validate numbers locally
|
|
5
|
+
(E.164, libphonenumber), and verify messenger registration via the official
|
|
6
|
+
REST API.
|
|
7
|
+
|
|
8
|
+
Public API:
|
|
9
|
+
validate_e164 — format-validate a raw phone string and return E.164.
|
|
10
|
+
is_mobile — check whether an E.164 number is mobile.
|
|
11
|
+
detect_region — guess the ISO region of a raw phone string.
|
|
12
|
+
normalize — convert any input to canonical E.164 form or None.
|
|
13
|
+
clean — strip non-digit characters (preserving leading +).
|
|
14
|
+
CheckMaxClient — client for the CheckMaxApp REST API (registration check).
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> from checkmax_phone_utils import normalize, validate_e164
|
|
18
|
+
>>> normalize("+7 (916) 123-45-67")
|
|
19
|
+
'+79161234567'
|
|
20
|
+
>>> validate_e164("89161234567", region="RU")
|
|
21
|
+
(True, '+79161234567')
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from checkmax_phone_utils.client import CheckMaxClient
|
|
25
|
+
from checkmax_phone_utils.exceptions import (
|
|
26
|
+
CheckMaxError,
|
|
27
|
+
InvalidPhoneNumberError,
|
|
28
|
+
APIError,
|
|
29
|
+
AuthError,
|
|
30
|
+
InsufficientBalanceError,
|
|
31
|
+
APINotAvailableError,
|
|
32
|
+
)
|
|
33
|
+
from checkmax_phone_utils.normalizer import clean, normalize
|
|
34
|
+
from checkmax_phone_utils.validator import (
|
|
35
|
+
detect_region,
|
|
36
|
+
is_mobile,
|
|
37
|
+
validate_e164,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__version__ = "0.2.0"
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"__version__",
|
|
44
|
+
"validate_e164",
|
|
45
|
+
"is_mobile",
|
|
46
|
+
"detect_region",
|
|
47
|
+
"normalize",
|
|
48
|
+
"clean",
|
|
49
|
+
"CheckMaxClient",
|
|
50
|
+
"CheckMaxError",
|
|
51
|
+
"InvalidPhoneNumberError",
|
|
52
|
+
"APIError",
|
|
53
|
+
"AuthError",
|
|
54
|
+
"InsufficientBalanceError",
|
|
55
|
+
"APINotAvailableError",
|
|
56
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Client for the CheckMaxApp REST API.
|
|
2
|
+
|
|
3
|
+
A small, dependency-free (stdlib ``urllib``) client for the CheckMaxApp
|
|
4
|
+
phone-validation API — verify whether a phone number is registered on the
|
|
5
|
+
MAX messenger and retrieve the public profile name, for anti-fraud,
|
|
6
|
+
list hygiene and CRM enrichment.
|
|
7
|
+
|
|
8
|
+
Get an API key from the CheckMaxApp Telegram bot, then:
|
|
9
|
+
|
|
10
|
+
>>> from checkmax_phone_utils import CheckMaxClient
|
|
11
|
+
>>> client = CheckMaxClient(api_key="mxk_...")
|
|
12
|
+
>>> client.check(["79001234567"]) # doctest: +SKIP
|
|
13
|
+
[{'phone': '79001234567', 'status': 'registered',
|
|
14
|
+
'first_name': 'Ivan', 'last_name': 'Petrov'}]
|
|
15
|
+
|
|
16
|
+
Full docs: https://checkmaxapp.com/api
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import urllib.error
|
|
23
|
+
import urllib.request
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from checkmax_phone_utils.exceptions import (
|
|
27
|
+
APIError,
|
|
28
|
+
AuthError,
|
|
29
|
+
InsufficientBalanceError,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = ["CheckMaxClient"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CheckMaxClient:
|
|
36
|
+
"""Client for the CheckMaxApp phone-validation REST API.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
api_key: Personal API key (``mxk_`` + 64 hex chars). Obtain and
|
|
40
|
+
rotate it via the CheckMaxApp Telegram bot. Sent only in the
|
|
41
|
+
``X-API-Key`` header — never in the query string.
|
|
42
|
+
base_url: API root. Defaults to the public endpoint.
|
|
43
|
+
timeout: Per-request HTTP timeout in seconds.
|
|
44
|
+
|
|
45
|
+
See https://checkmaxapp.com/api for the full reference.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
DEFAULT_BASE_URL = "https://api.maxcheck.online/v1"
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
api_key: str,
|
|
53
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
54
|
+
timeout: float = 30.0,
|
|
55
|
+
) -> None:
|
|
56
|
+
self.api_key = api_key
|
|
57
|
+
self.base_url = base_url.rstrip("/")
|
|
58
|
+
self.timeout = timeout
|
|
59
|
+
|
|
60
|
+
# ---- HTTP plumbing -------------------------------------------------
|
|
61
|
+
|
|
62
|
+
def _request(
|
|
63
|
+
self,
|
|
64
|
+
method: str,
|
|
65
|
+
path: str,
|
|
66
|
+
body: dict[str, Any] | None = None,
|
|
67
|
+
auth: bool = True,
|
|
68
|
+
) -> Any:
|
|
69
|
+
url = f"{self.base_url}{path}"
|
|
70
|
+
data = json.dumps(body).encode("utf-8") if body is not None else None
|
|
71
|
+
headers = {"Accept": "application/json"}
|
|
72
|
+
if data is not None:
|
|
73
|
+
headers["Content-Type"] = "application/json"
|
|
74
|
+
if auth:
|
|
75
|
+
headers["X-API-Key"] = self.api_key
|
|
76
|
+
|
|
77
|
+
req = urllib.request.Request(url, data=data, method=method, headers=headers)
|
|
78
|
+
try:
|
|
79
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
80
|
+
raw = resp.read().decode("utf-8")
|
|
81
|
+
return json.loads(raw) if raw else None
|
|
82
|
+
except urllib.error.HTTPError as exc:
|
|
83
|
+
detail = exc.read().decode("utf-8", "ignore")
|
|
84
|
+
if exc.code == 401:
|
|
85
|
+
raise AuthError(detail or "invalid or missing API key") from exc
|
|
86
|
+
if exc.code == 402:
|
|
87
|
+
raise InsufficientBalanceError(detail or "insufficient balance") from exc
|
|
88
|
+
raise APIError(f"HTTP {exc.code}: {detail[:300]}", status_code=exc.code) from exc
|
|
89
|
+
except urllib.error.URLError as exc:
|
|
90
|
+
raise APIError(f"network error: {exc.reason}") from exc
|
|
91
|
+
|
|
92
|
+
# ---- Endpoints -----------------------------------------------------
|
|
93
|
+
|
|
94
|
+
def health(self) -> dict[str, Any]:
|
|
95
|
+
"""``GET /health`` — service liveness for monitoring (no auth)."""
|
|
96
|
+
return self._request("GET", "/health", auth=False)
|
|
97
|
+
|
|
98
|
+
def balance(self) -> dict[str, Any]:
|
|
99
|
+
"""``GET /balance`` — current balance, tier discount and effective price."""
|
|
100
|
+
return self._request("GET", "/balance")
|
|
101
|
+
|
|
102
|
+
def usage(self) -> dict[str, Any]:
|
|
103
|
+
"""``GET /usage`` — counters of successfully billed checks per window."""
|
|
104
|
+
return self._request("GET", "/usage")
|
|
105
|
+
|
|
106
|
+
def check(self, phones: list[str]) -> list[dict[str, Any]]:
|
|
107
|
+
"""``POST /check`` — synchronously verify a list of phone numbers.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
phones: Phone numbers (digits, e.g. ``"79001234567"``). Best for
|
|
111
|
+
up to ~1000 numbers; for more, use :meth:`batch_create`.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
One result per input number, each with ``phone``, ``status``
|
|
115
|
+
(``registered`` / ``not_found`` / ``error``) and, when
|
|
116
|
+
registered, ``first_name`` / ``last_name``.
|
|
117
|
+
"""
|
|
118
|
+
return self._request("POST", "/check", {"phones": phones})
|
|
119
|
+
|
|
120
|
+
def batch_create(self, phones: list[str]) -> dict[str, Any]:
|
|
121
|
+
"""``POST /batch`` — submit an async batch job for large lists.
|
|
122
|
+
|
|
123
|
+
Returns a job descriptor including its ``id``; poll
|
|
124
|
+
:meth:`batch_status` and fetch results with :meth:`batch_download`.
|
|
125
|
+
"""
|
|
126
|
+
return self._request("POST", "/batch", {"phones": phones})
|
|
127
|
+
|
|
128
|
+
def batch_status(self, job_id: str) -> dict[str, Any]:
|
|
129
|
+
"""``GET /batch/{id}`` — status/progress of an async batch job."""
|
|
130
|
+
return self._request("GET", f"/batch/{job_id}")
|
|
131
|
+
|
|
132
|
+
def batch_download(self, job_id: str) -> bytes:
|
|
133
|
+
"""``GET /batch/{id}/download`` — fetch finished batch results (CSV bytes)."""
|
|
134
|
+
url = f"{self.base_url}/batch/{job_id}/download"
|
|
135
|
+
req = urllib.request.Request(
|
|
136
|
+
url, method="GET", headers={"X-API-Key": self.api_key}
|
|
137
|
+
)
|
|
138
|
+
try:
|
|
139
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
140
|
+
return resp.read()
|
|
141
|
+
except urllib.error.HTTPError as exc:
|
|
142
|
+
if exc.code == 401:
|
|
143
|
+
raise AuthError("invalid or missing API key") from exc
|
|
144
|
+
raise APIError(f"HTTP {exc.code}", status_code=exc.code) from exc
|
|
145
|
+
except urllib.error.URLError as exc:
|
|
146
|
+
raise APIError(f"network error: {exc.reason}") from exc
|