namecheap-python 1.1.0__tar.gz → 1.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.
- namecheap_python-1.2.0/CLAUDE.md +2 -0
- namecheap_python-1.2.0/CONTRIBUTING.md +113 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/PKG-INFO +83 -14
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/README.md +82 -13
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/pyproject.toml +1 -1
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/__init__.py +14 -2
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/_api/dns.py +35 -1
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/_api/domains.py +47 -1
- namecheap_python-1.2.0/src/namecheap/_api/users.py +32 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/client.py +8 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/models.py +60 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_cli/__main__.py +86 -50
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/uv.lock +1 -1
- namecheap_python-1.1.0/docs/dev/README.md +0 -220
- namecheap_python-1.1.0/pending.md +0 -94
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/.env.example +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/.github/cliff.toml +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/.github/workflows/release.yml +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/.gitignore +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/.pre-commit-config.yaml +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/CLI.md +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/LICENSE +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/MANIFEST.in +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/examples/README.md +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/examples/quickstart.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/_api/__init__.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/_api/base.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/errors.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap/logging.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_cli/README.md +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_cli/__init__.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_cli/completion.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/README.md +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/__init__.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/__main__.py +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/assets/screenshot1.png +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/assets/screenshot2.png +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/assets/screenshot3.png +0 -0
- {namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/assets/screenshot4.png +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/adriangalilea/namecheap-python.git
|
|
7
|
+
cd namecheap-python
|
|
8
|
+
uv sync --all-extras
|
|
9
|
+
pre-commit install
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Code Quality
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv run ruff check # lint
|
|
16
|
+
uv run ruff check --fix # auto-fix
|
|
17
|
+
uv run ruff format # format
|
|
18
|
+
|
|
19
|
+
pre-commit run --all-files # run all hooks
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Building
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv build
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Release
|
|
29
|
+
|
|
30
|
+
Automated via GitHub Actions on push to `main`. If the version in `pyproject.toml` doesn't exist on PyPI, CI publishes automatically, creates a git tag, and generates a GitHub release with changelog via [git-cliff](https://git-cliff.org/).
|
|
31
|
+
|
|
32
|
+
Bump version in both `pyproject.toml` and `src/namecheap/__init__.py`.
|
|
33
|
+
|
|
34
|
+
## Debugging
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import logging
|
|
38
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or set `NAMECHEAP_DEBUG=true`.
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
### Project Structure
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
src/
|
|
49
|
+
├── namecheap/ # Core SDK
|
|
50
|
+
│ ├── client.py # Main client — wires up API classes
|
|
51
|
+
│ ├── models.py # Pydantic models (all API data types live here)
|
|
52
|
+
│ ├── errors.py # Exceptions with helpful messages
|
|
53
|
+
│ ├── logging.py # Colored logging and error display
|
|
54
|
+
│ └── _api/ # API implementations
|
|
55
|
+
│ ├── base.py # BaseAPI with _request() — all API calls go through here
|
|
56
|
+
│ ├── domains.py # namecheap.domains.* endpoints
|
|
57
|
+
│ └── dns.py # namecheap.domains.dns.* endpoints + builder
|
|
58
|
+
├── namecheap_cli/ # CLI (click)
|
|
59
|
+
│ ├── __main__.py # All commands in one file
|
|
60
|
+
│ └── completion.py # Shell completions
|
|
61
|
+
└── namecheap_dns_tui/ # TUI (textual)
|
|
62
|
+
└── __main__.py
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Key Patterns
|
|
66
|
+
|
|
67
|
+
**SDK layer** (`src/namecheap/_api/`):
|
|
68
|
+
- Every API class extends `BaseAPI` which provides `_request(command, params, model=, path=)`
|
|
69
|
+
- `_request` handles auth params, XML parsing, error handling, and Pydantic deserialization
|
|
70
|
+
- Pass `model=SomeModel` to auto-parse, or omit for raw dict access
|
|
71
|
+
- Pass `path="DotSeparated.Path"` to navigate the XML response
|
|
72
|
+
- Domain parsing uses `tldextract` to split into SLD/TLD — see any method in `dns.py`
|
|
73
|
+
- Return Pydantic models, not dicts — see `Nameservers`, `DNSRecord`, `Domain`, etc.
|
|
74
|
+
- Use `assert` for assumptions, `ValueError` for bad user input
|
|
75
|
+
|
|
76
|
+
**Models** (`src/namecheap/models.py`):
|
|
77
|
+
- `XMLModel` base handles XML attribute aliases (`@Name` → `name`) and boolean parsing
|
|
78
|
+
- All new data types go here and get exported from `__init__.py`
|
|
79
|
+
|
|
80
|
+
**CLI layer** (`src/namecheap_cli/__main__.py`):
|
|
81
|
+
- Commands use `@pass_config` decorator for client initialization
|
|
82
|
+
- Interactive operations use `rich.progress.Progress` with `SpinnerColumn` for loading
|
|
83
|
+
- Destructive operations use `rich.prompt.Confirm.ask` with `--yes/-y` to skip
|
|
84
|
+
- Table output via `rich.table.Table`, with `output_formatter()` for JSON/YAML alternatives
|
|
85
|
+
- Error handling: catch `NamecheapError`, print with `[red]`, `sys.exit(1)`
|
|
86
|
+
|
|
87
|
+
**DNS Builder** (`src/namecheap/_api/dns.py`):
|
|
88
|
+
- Fluent interface: `builder.a(...).mx(...).txt(...)` returns `Self` for chaining
|
|
89
|
+
- Default TTL is 1799 (displays as "Automatic" in Namecheap UI)
|
|
90
|
+
|
|
91
|
+
### Adding a New API Endpoint
|
|
92
|
+
|
|
93
|
+
1. Add Pydantic model in `models.py`, export from `__init__.py`
|
|
94
|
+
2. Add method in the appropriate `_api/*.py` file using `self._request()`
|
|
95
|
+
3. Add CLI command in `__main__.py` following existing patterns
|
|
96
|
+
4. Update the API Coverage table in `README.md`
|
|
97
|
+
|
|
98
|
+
Best reference: `dns.py:get_nameservers()` for SDK, `__main__.py:dns_nameservers()` for CLI.
|
|
99
|
+
|
|
100
|
+
## PR Checklist
|
|
101
|
+
|
|
102
|
+
- [ ] `uv run ruff check` passes
|
|
103
|
+
- [ ] `uv run ruff format --check` passes
|
|
104
|
+
- [ ] Examples work correctly
|
|
105
|
+
- [ ] Documentation updated if needed
|
|
106
|
+
|
|
107
|
+
## Links
|
|
108
|
+
|
|
109
|
+
- [Namecheap API Documentation](https://www.namecheap.com/support/api/methods/)
|
|
110
|
+
- [Pydantic Documentation](https://docs.pydantic.dev/)
|
|
111
|
+
- [Click Documentation](https://click.palletsprojects.com/)
|
|
112
|
+
- [Textual Documentation](https://textual.textualize.io/)
|
|
113
|
+
- [Ruff Documentation](https://docs.astral.sh/ruff/)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: namecheap-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A friendly Python SDK for Namecheap API
|
|
5
5
|
Project-URL: Homepage, https://github.com/adriangalilea/namecheap-python
|
|
6
6
|
Project-URL: Repository, https://github.com/adriangalilea/namecheap-python
|
|
@@ -42,6 +42,12 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
# Namecheap Python SDK
|
|
44
44
|
|
|
45
|
+
[](https://pypi.org/project/namecheap-python/)
|
|
46
|
+
[](https://pepy.tech/project/namecheap-python)
|
|
47
|
+
[](https://pepy.tech/project/namecheap-python)
|
|
48
|
+
[](https://pypi.org/project/namecheap-python/)
|
|
49
|
+
[](https://opensource.org/licenses/MIT)
|
|
50
|
+
|
|
45
51
|
A modern, friendly Python SDK for the Namecheap API with comprehensive CLI and TUI tools.
|
|
46
52
|
|
|
47
53
|
## 🚀 Features
|
|
@@ -206,6 +212,38 @@ Adding CNAME record to tdo.garden...
|
|
|
206
212
|
```
|
|
207
213
|
|
|
208
214
|
|
|
215
|
+
Check account balance:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
❯ namecheap-cli account balance
|
|
219
|
+
Account Balance
|
|
220
|
+
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
|
|
221
|
+
┃ Field ┃ Amount ┃
|
|
222
|
+
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
|
|
223
|
+
│ Available Balance │ 0.00 USD │
|
|
224
|
+
│ Account Balance │ 0.00 USD │
|
|
225
|
+
│ Earned Amount │ 0.00 USD │
|
|
226
|
+
│ Withdrawable │ 0.00 USD │
|
|
227
|
+
│ Auto-Renew Required │ 20.16 USD │
|
|
228
|
+
└─────────────────────┴───────────┘
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Get detailed domain info:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
❯ namecheap-cli domain info self.fm
|
|
235
|
+
|
|
236
|
+
Domain Information: self.fm
|
|
237
|
+
|
|
238
|
+
Status: Ok
|
|
239
|
+
Owner: adriangalilea
|
|
240
|
+
Created: 07/15/2023
|
|
241
|
+
Expires: 07/15/2026
|
|
242
|
+
Premium: No
|
|
243
|
+
WHOIS Guard: ✓ Enabled
|
|
244
|
+
DNS Provider: CUSTOM
|
|
245
|
+
```
|
|
246
|
+
|
|
209
247
|
You can also export DNS records:
|
|
210
248
|
|
|
211
249
|
```bash
|
|
@@ -320,6 +358,33 @@ nc.dns.set_custom_nameservers("example.com", [
|
|
|
320
358
|
nc.dns.set_default_nameservers("example.com")
|
|
321
359
|
```
|
|
322
360
|
|
|
361
|
+
### Domain Info
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
info = nc.domains.get_info("example.com")
|
|
365
|
+
print(info.status) # 'Ok'
|
|
366
|
+
print(info.whoisguard_enabled) # True
|
|
367
|
+
print(info.dns_provider) # 'CUSTOM'
|
|
368
|
+
print(info.created) # '07/15/2023'
|
|
369
|
+
print(info.expires) # '07/15/2026'
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Account Balance
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
bal = nc.users.get_balances()
|
|
376
|
+
print(f"{bal.available_balance} {bal.currency}") # '4932.96 USD'
|
|
377
|
+
print(bal.funds_required_for_auto_renew) # Decimal('20.16')
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Email Forwarding
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
rules = nc.dns.get_email_forwarding("example.com")
|
|
384
|
+
for r in rules:
|
|
385
|
+
print(f"{r.mailbox} -> {r.forward_to}")
|
|
386
|
+
```
|
|
387
|
+
|
|
323
388
|
### Domain Management
|
|
324
389
|
|
|
325
390
|
```python
|
|
@@ -393,22 +458,24 @@ nc.dns.builder().a("www", "192.0.2.1", ttl=1799) # Shows as "Automatic"
|
|
|
393
458
|
nc.dns.builder().a("www", "192.0.2.1", ttl=1800) # Shows as "30 min"
|
|
394
459
|
```
|
|
395
460
|
|
|
396
|
-
##
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
461
|
+
## 📊 [API Coverage](https://www.namecheap.com/support/api/methods/)
|
|
462
|
+
|
|
463
|
+
| API | Status | Methods |
|
|
464
|
+
|-----|--------|---------|
|
|
465
|
+
| `namecheap.domains.*` | ✅ Done | `check`, `list`, `getInfo`, `register`, `renew`, `setContacts`, `lock`/`unlock` |
|
|
466
|
+
| `namecheap.domains.dns.*` | ✅ Done | `getHosts`, `setHosts` (builder pattern), `add`, `delete`, `export`, `getList`, `setCustom`, `setDefault`, `getEmailForwarding` |
|
|
467
|
+
| `namecheap.users.*` | ⚠️ Partial | `getBalances`, `getPricing` (needs debugging). Planned: `changePassword`, `update`, `create`, `login`, `resetPassword` |
|
|
468
|
+
| `namecheap.domains.*` | 🚧 Planned | `getContacts`, `getTldList`, `reactivate` |
|
|
469
|
+
| `namecheap.domains.dns.*` | 🚧 Planned | `setEmailForwarding` |
|
|
470
|
+
| `namecheap.users.address.*` | 🚧 Planned | `create`, `delete`, `getInfo`, `getList`, `setDefault`, `update` |
|
|
471
|
+
| `namecheap.ssl.*` | 🚧 Planned | `create`, `activate`, `renew`, `revoke`, `getList`, `getInfo`, `parseCSR`, `reissue`, and more |
|
|
472
|
+
| `namecheap.domains.transfer.*` | 🚧 Planned | `create`, `getStatus`, `updateStatus`, `getList` |
|
|
473
|
+
| `namecheap.domains.ns.*` | 🚧 Planned | Glue records — `create`, `delete`, `getInfo`, `update` |
|
|
474
|
+
| `namecheap.domainprivacy.*` | 🚧 Planned | `enable`, `disable`, `renew`, `getList`, `changeemailaddress` |
|
|
408
475
|
|
|
409
476
|
## 🛠️ Development
|
|
410
477
|
|
|
411
|
-
See [
|
|
478
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and development guidelines.
|
|
412
479
|
|
|
413
480
|
## 📝 License
|
|
414
481
|
|
|
@@ -420,4 +487,6 @@ Contributions are welcome! Please feel free to submit a Pull Request. See the [D
|
|
|
420
487
|
|
|
421
488
|
### Contributors
|
|
422
489
|
|
|
490
|
+
- [@huntertur](https://github.com/huntertur) — Rich dependency fix
|
|
491
|
+
- [@jeffmcadams](https://github.com/jeffmcadams) — Domain serialization round-trip
|
|
423
492
|
- [@cosmin](https://github.com/cosmin) — Nameserver management
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Namecheap Python SDK
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/namecheap-python/)
|
|
4
|
+
[](https://pepy.tech/project/namecheap-python)
|
|
5
|
+
[](https://pepy.tech/project/namecheap-python)
|
|
6
|
+
[](https://pypi.org/project/namecheap-python/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
3
9
|
A modern, friendly Python SDK for the Namecheap API with comprehensive CLI and TUI tools.
|
|
4
10
|
|
|
5
11
|
## 🚀 Features
|
|
@@ -164,6 +170,38 @@ Adding CNAME record to tdo.garden...
|
|
|
164
170
|
```
|
|
165
171
|
|
|
166
172
|
|
|
173
|
+
Check account balance:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
❯ namecheap-cli account balance
|
|
177
|
+
Account Balance
|
|
178
|
+
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
|
|
179
|
+
┃ Field ┃ Amount ┃
|
|
180
|
+
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
|
|
181
|
+
│ Available Balance │ 0.00 USD │
|
|
182
|
+
│ Account Balance │ 0.00 USD │
|
|
183
|
+
│ Earned Amount │ 0.00 USD │
|
|
184
|
+
│ Withdrawable │ 0.00 USD │
|
|
185
|
+
│ Auto-Renew Required │ 20.16 USD │
|
|
186
|
+
└─────────────────────┴───────────┘
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Get detailed domain info:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
❯ namecheap-cli domain info self.fm
|
|
193
|
+
|
|
194
|
+
Domain Information: self.fm
|
|
195
|
+
|
|
196
|
+
Status: Ok
|
|
197
|
+
Owner: adriangalilea
|
|
198
|
+
Created: 07/15/2023
|
|
199
|
+
Expires: 07/15/2026
|
|
200
|
+
Premium: No
|
|
201
|
+
WHOIS Guard: ✓ Enabled
|
|
202
|
+
DNS Provider: CUSTOM
|
|
203
|
+
```
|
|
204
|
+
|
|
167
205
|
You can also export DNS records:
|
|
168
206
|
|
|
169
207
|
```bash
|
|
@@ -278,6 +316,33 @@ nc.dns.set_custom_nameservers("example.com", [
|
|
|
278
316
|
nc.dns.set_default_nameservers("example.com")
|
|
279
317
|
```
|
|
280
318
|
|
|
319
|
+
### Domain Info
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
info = nc.domains.get_info("example.com")
|
|
323
|
+
print(info.status) # 'Ok'
|
|
324
|
+
print(info.whoisguard_enabled) # True
|
|
325
|
+
print(info.dns_provider) # 'CUSTOM'
|
|
326
|
+
print(info.created) # '07/15/2023'
|
|
327
|
+
print(info.expires) # '07/15/2026'
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Account Balance
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
bal = nc.users.get_balances()
|
|
334
|
+
print(f"{bal.available_balance} {bal.currency}") # '4932.96 USD'
|
|
335
|
+
print(bal.funds_required_for_auto_renew) # Decimal('20.16')
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Email Forwarding
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
rules = nc.dns.get_email_forwarding("example.com")
|
|
342
|
+
for r in rules:
|
|
343
|
+
print(f"{r.mailbox} -> {r.forward_to}")
|
|
344
|
+
```
|
|
345
|
+
|
|
281
346
|
### Domain Management
|
|
282
347
|
|
|
283
348
|
```python
|
|
@@ -351,22 +416,24 @@ nc.dns.builder().a("www", "192.0.2.1", ttl=1799) # Shows as "Automatic"
|
|
|
351
416
|
nc.dns.builder().a("www", "192.0.2.1", ttl=1800) # Shows as "30 min"
|
|
352
417
|
```
|
|
353
418
|
|
|
354
|
-
##
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
419
|
+
## 📊 [API Coverage](https://www.namecheap.com/support/api/methods/)
|
|
420
|
+
|
|
421
|
+
| API | Status | Methods |
|
|
422
|
+
|-----|--------|---------|
|
|
423
|
+
| `namecheap.domains.*` | ✅ Done | `check`, `list`, `getInfo`, `register`, `renew`, `setContacts`, `lock`/`unlock` |
|
|
424
|
+
| `namecheap.domains.dns.*` | ✅ Done | `getHosts`, `setHosts` (builder pattern), `add`, `delete`, `export`, `getList`, `setCustom`, `setDefault`, `getEmailForwarding` |
|
|
425
|
+
| `namecheap.users.*` | ⚠️ Partial | `getBalances`, `getPricing` (needs debugging). Planned: `changePassword`, `update`, `create`, `login`, `resetPassword` |
|
|
426
|
+
| `namecheap.domains.*` | 🚧 Planned | `getContacts`, `getTldList`, `reactivate` |
|
|
427
|
+
| `namecheap.domains.dns.*` | 🚧 Planned | `setEmailForwarding` |
|
|
428
|
+
| `namecheap.users.address.*` | 🚧 Planned | `create`, `delete`, `getInfo`, `getList`, `setDefault`, `update` |
|
|
429
|
+
| `namecheap.ssl.*` | 🚧 Planned | `create`, `activate`, `renew`, `revoke`, `getList`, `getInfo`, `parseCSR`, `reissue`, and more |
|
|
430
|
+
| `namecheap.domains.transfer.*` | 🚧 Planned | `create`, `getStatus`, `updateStatus`, `getList` |
|
|
431
|
+
| `namecheap.domains.ns.*` | 🚧 Planned | Glue records — `create`, `delete`, `getInfo`, `update` |
|
|
432
|
+
| `namecheap.domainprivacy.*` | 🚧 Planned | `enable`, `disable`, `renew`, `getList`, `changeemailaddress` |
|
|
366
433
|
|
|
367
434
|
## 🛠️ Development
|
|
368
435
|
|
|
369
|
-
See [
|
|
436
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and development guidelines.
|
|
370
437
|
|
|
371
438
|
## 📝 License
|
|
372
439
|
|
|
@@ -378,4 +445,6 @@ Contributions are welcome! Please feel free to submit a Pull Request. See the [D
|
|
|
378
445
|
|
|
379
446
|
### Contributors
|
|
380
447
|
|
|
448
|
+
- [@huntertur](https://github.com/huntertur) — Rich dependency fix
|
|
449
|
+
- [@jeffmcadams](https://github.com/jeffmcadams) — Domain serialization round-trip
|
|
381
450
|
- [@cosmin](https://github.com/cosmin) — Nameserver management
|
|
@@ -12,15 +12,27 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
from .client import Namecheap
|
|
14
14
|
from .errors import ConfigurationError, NamecheapError, ValidationError
|
|
15
|
-
from .models import
|
|
15
|
+
from .models import (
|
|
16
|
+
AccountBalance,
|
|
17
|
+
Contact,
|
|
18
|
+
DNSRecord,
|
|
19
|
+
Domain,
|
|
20
|
+
DomainCheck,
|
|
21
|
+
DomainInfo,
|
|
22
|
+
EmailForward,
|
|
23
|
+
Nameservers,
|
|
24
|
+
)
|
|
16
25
|
|
|
17
|
-
__version__ = "1.
|
|
26
|
+
__version__ = "1.2.0"
|
|
18
27
|
__all__ = [
|
|
28
|
+
"AccountBalance",
|
|
19
29
|
"ConfigurationError",
|
|
20
30
|
"Contact",
|
|
21
31
|
"DNSRecord",
|
|
22
32
|
"Domain",
|
|
23
33
|
"DomainCheck",
|
|
34
|
+
"DomainInfo",
|
|
35
|
+
"EmailForward",
|
|
24
36
|
"Namecheap",
|
|
25
37
|
"NamecheapError",
|
|
26
38
|
"Nameservers",
|
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, Literal
|
|
|
6
6
|
|
|
7
7
|
import tldextract
|
|
8
8
|
|
|
9
|
-
from namecheap.models import DNSRecord, Nameservers
|
|
9
|
+
from namecheap.models import DNSRecord, EmailForward, Nameservers
|
|
10
10
|
|
|
11
11
|
from .base import BaseAPI
|
|
12
12
|
|
|
@@ -494,3 +494,37 @@ class DnsAPI(BaseAPI):
|
|
|
494
494
|
nameservers = [ns_data] if isinstance(ns_data, str) else ns_data
|
|
495
495
|
|
|
496
496
|
return Nameservers(is_default=is_default, nameservers=nameservers)
|
|
497
|
+
|
|
498
|
+
def get_email_forwarding(self, domain: str) -> list[EmailForward]:
|
|
499
|
+
"""
|
|
500
|
+
Get email forwarding rules for a domain.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
domain: Domain name
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
List of EmailForward rules
|
|
507
|
+
|
|
508
|
+
Examples:
|
|
509
|
+
>>> rules = nc.dns.get_email_forwarding("example.com")
|
|
510
|
+
>>> for r in rules:
|
|
511
|
+
... print(f"{r.mailbox} -> {r.forward_to}")
|
|
512
|
+
"""
|
|
513
|
+
result: Any = self._request(
|
|
514
|
+
"namecheap.domains.dns.getEmailForwarding",
|
|
515
|
+
{"DomainName": domain},
|
|
516
|
+
path="DomainDNSGetEmailForwardingResult",
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
if not result:
|
|
520
|
+
return []
|
|
521
|
+
|
|
522
|
+
forwards = result.get("Forward", [])
|
|
523
|
+
if isinstance(forwards, dict):
|
|
524
|
+
forwards = [forwards]
|
|
525
|
+
assert isinstance(forwards, list), f"Unexpected Forward type: {type(forwards)}"
|
|
526
|
+
|
|
527
|
+
return [
|
|
528
|
+
EmailForward(mailbox=f.get("@mailbox", ""), forward_to=f.get("#text", ""))
|
|
529
|
+
for f in forwards
|
|
530
|
+
]
|
|
@@ -9,7 +9,7 @@ from typing import Any
|
|
|
9
9
|
import tldextract
|
|
10
10
|
|
|
11
11
|
from namecheap.logging import logger
|
|
12
|
-
from namecheap.models import Contact, Domain, DomainCheck
|
|
12
|
+
from namecheap.models import Contact, Domain, DomainCheck, DomainInfo
|
|
13
13
|
|
|
14
14
|
from .base import BaseAPI
|
|
15
15
|
|
|
@@ -110,6 +110,52 @@ class DomainsAPI(BaseAPI):
|
|
|
110
110
|
return [results]
|
|
111
111
|
return results if isinstance(results, list) else []
|
|
112
112
|
|
|
113
|
+
def get_info(self, domain: str) -> DomainInfo:
|
|
114
|
+
"""
|
|
115
|
+
Get detailed information about a domain.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
domain: Domain name
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
DomainInfo with status, whoisguard, DNS provider, etc.
|
|
122
|
+
|
|
123
|
+
Examples:
|
|
124
|
+
>>> info = nc.domains.get_info("example.com")
|
|
125
|
+
>>> print(f"{info.domain} status={info.status} whoisguard={info.whoisguard_enabled}")
|
|
126
|
+
"""
|
|
127
|
+
result: Any = self._request(
|
|
128
|
+
"namecheap.domains.getInfo",
|
|
129
|
+
{"DomainName": domain},
|
|
130
|
+
path="DomainGetInfoResult",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
assert result, f"API returned empty result for {domain} getInfo"
|
|
134
|
+
|
|
135
|
+
# Extract nested fields into flat structure
|
|
136
|
+
domain_details = result.get("DomainDetails", {})
|
|
137
|
+
whoisguard = result.get("Whoisguard", {})
|
|
138
|
+
dns_details = result.get("DnsDetails", {})
|
|
139
|
+
|
|
140
|
+
flat = {
|
|
141
|
+
"@ID": result.get("@ID"),
|
|
142
|
+
"@DomainName": result.get("@DomainName"),
|
|
143
|
+
"@OwnerName": result.get("@OwnerName"),
|
|
144
|
+
"@IsOwner": result.get("@IsOwner"),
|
|
145
|
+
"@IsPremium": result.get("@IsPremium", "false"),
|
|
146
|
+
"@Status": result.get("@Status"),
|
|
147
|
+
"created": domain_details.get("CreatedDate"),
|
|
148
|
+
"expires": domain_details.get("ExpiredDate"),
|
|
149
|
+
"whoisguard_enabled": whoisguard.get("@Enabled", "false").lower() == "true"
|
|
150
|
+
if isinstance(whoisguard, dict)
|
|
151
|
+
else False,
|
|
152
|
+
"dns_provider": dns_details.get("@ProviderType")
|
|
153
|
+
if isinstance(dns_details, dict)
|
|
154
|
+
else None,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return DomainInfo.model_validate(flat)
|
|
158
|
+
|
|
113
159
|
def register(
|
|
114
160
|
self,
|
|
115
161
|
domain: str,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Users API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from namecheap.models import AccountBalance
|
|
8
|
+
|
|
9
|
+
from .base import BaseAPI
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UsersAPI(BaseAPI):
|
|
13
|
+
"""User account operations."""
|
|
14
|
+
|
|
15
|
+
def get_balances(self) -> AccountBalance:
|
|
16
|
+
"""
|
|
17
|
+
Get account balance information.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
AccountBalance with available balance, earned amount, etc.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
>>> bal = nc.users.get_balances()
|
|
24
|
+
>>> print(f"{bal.available_balance} {bal.currency}")
|
|
25
|
+
"""
|
|
26
|
+
result: Any = self._request(
|
|
27
|
+
"namecheap.users.getBalances",
|
|
28
|
+
path="UserGetBalancesResult",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
assert result, "API returned empty result for getBalances"
|
|
32
|
+
return AccountBalance.model_validate(result)
|
|
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
|
|
|
16
16
|
|
|
17
17
|
from ._api.dns import DnsAPI
|
|
18
18
|
from ._api.domains import DomainsAPI
|
|
19
|
+
from ._api.users import UsersAPI
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class Namecheap:
|
|
@@ -110,6 +111,13 @@ class Namecheap:
|
|
|
110
111
|
|
|
111
112
|
return DnsAPI(self)
|
|
112
113
|
|
|
114
|
+
@cached_property
|
|
115
|
+
def users(self) -> UsersAPI:
|
|
116
|
+
"""User account operations."""
|
|
117
|
+
from ._api.users import UsersAPI
|
|
118
|
+
|
|
119
|
+
return UsersAPI(self)
|
|
120
|
+
|
|
113
121
|
def __enter__(self) -> Self:
|
|
114
122
|
"""Enter context manager."""
|
|
115
123
|
return self
|
|
@@ -251,6 +251,66 @@ class Domain(XMLModel):
|
|
|
251
251
|
raise ValueError(f"Cannot parse datetime from {v}")
|
|
252
252
|
|
|
253
253
|
|
|
254
|
+
class AccountBalance(BaseModel):
|
|
255
|
+
"""Account balance information."""
|
|
256
|
+
|
|
257
|
+
currency: str = Field(alias="@Currency")
|
|
258
|
+
available_balance: Decimal = Field(alias="@AvailableBalance")
|
|
259
|
+
account_balance: Decimal = Field(alias="@AccountBalance")
|
|
260
|
+
earned_amount: Decimal = Field(alias="@EarnedAmount")
|
|
261
|
+
withdrawable_amount: Decimal = Field(alias="@WithdrawableAmount")
|
|
262
|
+
funds_required_for_auto_renew: Decimal = Field(alias="@FundsRequiredForAutoRenew")
|
|
263
|
+
|
|
264
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
265
|
+
|
|
266
|
+
@field_validator(
|
|
267
|
+
"available_balance",
|
|
268
|
+
"account_balance",
|
|
269
|
+
"earned_amount",
|
|
270
|
+
"withdrawable_amount",
|
|
271
|
+
"funds_required_for_auto_renew",
|
|
272
|
+
mode="before",
|
|
273
|
+
)
|
|
274
|
+
@classmethod
|
|
275
|
+
def parse_decimal(cls, v: Any) -> Decimal:
|
|
276
|
+
return Decimal(str(v)) if v is not None else Decimal("0")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class DomainInfo(BaseModel):
|
|
280
|
+
"""Detailed domain information from getInfo."""
|
|
281
|
+
|
|
282
|
+
id: int = Field(alias="@ID")
|
|
283
|
+
domain: str = Field(alias="@DomainName")
|
|
284
|
+
owner: str = Field(alias="@OwnerName")
|
|
285
|
+
is_owner: bool = Field(alias="@IsOwner")
|
|
286
|
+
is_premium: bool = Field(alias="@IsPremium", default=False)
|
|
287
|
+
status: str = Field(alias="@Status")
|
|
288
|
+
created: str | None = Field(default=None)
|
|
289
|
+
expires: str | None = Field(default=None)
|
|
290
|
+
whoisguard_enabled: bool = Field(default=False)
|
|
291
|
+
dns_provider: str | None = Field(default=None)
|
|
292
|
+
|
|
293
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
294
|
+
|
|
295
|
+
@field_validator("is_owner", "is_premium", mode="before")
|
|
296
|
+
@classmethod
|
|
297
|
+
def parse_bool(cls, v: Any) -> bool:
|
|
298
|
+
if isinstance(v, bool):
|
|
299
|
+
return v
|
|
300
|
+
if isinstance(v, str):
|
|
301
|
+
return v.lower() == "true"
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class EmailForward(BaseModel):
|
|
306
|
+
"""Email forwarding rule."""
|
|
307
|
+
|
|
308
|
+
mailbox: str = Field(alias="@mailbox")
|
|
309
|
+
forward_to: str
|
|
310
|
+
|
|
311
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
312
|
+
|
|
313
|
+
|
|
254
314
|
class Nameservers(BaseModel):
|
|
255
315
|
"""Current nameserver configuration for a domain."""
|
|
256
316
|
|
|
@@ -367,57 +367,31 @@ def domain_info(config: Config, domain: str) -> None:
|
|
|
367
367
|
nc = config.init_client()
|
|
368
368
|
|
|
369
369
|
try:
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
370
|
+
with Progress(
|
|
371
|
+
SpinnerColumn(),
|
|
372
|
+
TextColumn("[progress.description]{task.description}"),
|
|
373
|
+
transient=True,
|
|
374
|
+
) as progress:
|
|
375
|
+
progress.add_task(f"Getting info for {domain}...", total=None)
|
|
376
|
+
info = nc.domains.get_info(domain)
|
|
376
377
|
|
|
377
378
|
if config.output_format == "table":
|
|
378
379
|
console.print(f"\n[bold cyan]Domain Information: {domain}[/bold cyan]\n")
|
|
379
|
-
console.print(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
f"[bold]
|
|
385
|
-
)
|
|
386
|
-
console.print(
|
|
387
|
-
f"[bold]Expires:[/bold] {domain_obj.expires.strftime('%Y-%m-%d')}"
|
|
388
|
-
)
|
|
389
|
-
console.print(
|
|
390
|
-
f"[bold]Auto-Renew:[/bold] {'✓ Enabled' if domain_obj.auto_renew else '✗ Disabled'}"
|
|
391
|
-
)
|
|
392
|
-
console.print(
|
|
393
|
-
f"[bold]Locked:[/bold] {'🔒 Yes' if domain_obj.is_locked else '🔓 No'}"
|
|
394
|
-
)
|
|
380
|
+
console.print(f"[bold]Status:[/bold] {info.status}")
|
|
381
|
+
console.print(f"[bold]Owner:[/bold] {info.owner}")
|
|
382
|
+
if info.created:
|
|
383
|
+
console.print(f"[bold]Created:[/bold] {info.created}")
|
|
384
|
+
if info.expires:
|
|
385
|
+
console.print(f"[bold]Expires:[/bold] {info.expires}")
|
|
386
|
+
console.print(f"[bold]Premium:[/bold] {'Yes' if info.is_premium else 'No'}")
|
|
395
387
|
console.print(
|
|
396
388
|
f"[bold]WHOIS Guard:[/bold] "
|
|
397
|
-
f"{'✓ Enabled' if
|
|
389
|
+
f"{'✓ Enabled' if info.whoisguard_enabled else '✗ Disabled'}"
|
|
398
390
|
)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
days_left = (domain_obj.expires - datetime.now()).days
|
|
402
|
-
if days_left < 30:
|
|
403
|
-
console.print(
|
|
404
|
-
f"\n⚠️ [yellow]Domain expires in {days_left} days![/yellow]"
|
|
405
|
-
)
|
|
406
|
-
elif days_left < 60:
|
|
407
|
-
console.print(f"\n📅 Domain expires in {days_left} days")
|
|
408
|
-
|
|
391
|
+
if info.dns_provider:
|
|
392
|
+
console.print(f"[bold]DNS Provider:[/bold] {info.dns_provider}")
|
|
409
393
|
else:
|
|
410
|
-
|
|
411
|
-
"domain": domain_obj.name,
|
|
412
|
-
"status": "active" if not domain_obj.is_expired else "expired",
|
|
413
|
-
"created": domain_obj.created.isoformat(),
|
|
414
|
-
"expires": domain_obj.expires.isoformat(),
|
|
415
|
-
"auto_renew": domain_obj.auto_renew,
|
|
416
|
-
"locked": domain_obj.is_locked,
|
|
417
|
-
"whois_guard": domain_obj.whois_guard,
|
|
418
|
-
"days_until_expiration": (domain_obj.expires - datetime.now()).days,
|
|
419
|
-
}
|
|
420
|
-
output_formatter(data, config.output_format)
|
|
394
|
+
output_formatter(info.model_dump(), config.output_format)
|
|
421
395
|
|
|
422
396
|
except NamecheapError as e:
|
|
423
397
|
console.print(f"[red]❌ Error: {e}[/red]")
|
|
@@ -906,6 +880,45 @@ def dns_export(config: Config, domain: str, format: str, output) -> None:
|
|
|
906
880
|
sys.exit(1)
|
|
907
881
|
|
|
908
882
|
|
|
883
|
+
@dns_group.command("email-forwarding")
|
|
884
|
+
@click.argument("domain")
|
|
885
|
+
@pass_config
|
|
886
|
+
def dns_email_forwarding(config: Config, domain: str) -> None:
|
|
887
|
+
"""Show email forwarding rules for a domain."""
|
|
888
|
+
nc = config.init_client()
|
|
889
|
+
|
|
890
|
+
try:
|
|
891
|
+
with Progress(
|
|
892
|
+
SpinnerColumn(),
|
|
893
|
+
TextColumn("[progress.description]{task.description}"),
|
|
894
|
+
transient=True,
|
|
895
|
+
) as progress:
|
|
896
|
+
progress.add_task(f"Getting email forwarding for {domain}...", total=None)
|
|
897
|
+
rules = nc.dns.get_email_forwarding(domain)
|
|
898
|
+
|
|
899
|
+
if config.output_format == "table":
|
|
900
|
+
if not rules:
|
|
901
|
+
console.print(
|
|
902
|
+
f"\n[yellow]No email forwarding rules for {domain}[/yellow]"
|
|
903
|
+
)
|
|
904
|
+
return
|
|
905
|
+
|
|
906
|
+
table = Table(title=f"Email Forwarding for {domain}")
|
|
907
|
+
table.add_column("Mailbox", style="cyan")
|
|
908
|
+
table.add_column("Forwards To", style="green")
|
|
909
|
+
|
|
910
|
+
for rule in rules:
|
|
911
|
+
table.add_row(f"{rule.mailbox}@{domain}", rule.forward_to)
|
|
912
|
+
|
|
913
|
+
console.print(table)
|
|
914
|
+
else:
|
|
915
|
+
output_formatter([r.model_dump() for r in rules], config.output_format)
|
|
916
|
+
|
|
917
|
+
except NamecheapError as e:
|
|
918
|
+
console.print(f"[red]❌ Error: {e}[/red]")
|
|
919
|
+
sys.exit(1)
|
|
920
|
+
|
|
921
|
+
|
|
909
922
|
@cli.group("account")
|
|
910
923
|
def account_group() -> None:
|
|
911
924
|
"""Account management commands."""
|
|
@@ -916,14 +929,37 @@ def account_group() -> None:
|
|
|
916
929
|
@pass_config
|
|
917
930
|
def account_balance(config: Config) -> None:
|
|
918
931
|
"""Check account balance."""
|
|
919
|
-
config.init_client()
|
|
932
|
+
nc = config.init_client()
|
|
920
933
|
|
|
921
934
|
try:
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
"[
|
|
925
|
-
|
|
926
|
-
|
|
935
|
+
with Progress(
|
|
936
|
+
SpinnerColumn(),
|
|
937
|
+
TextColumn("[progress.description]{task.description}"),
|
|
938
|
+
transient=True,
|
|
939
|
+
) as progress:
|
|
940
|
+
progress.add_task("Getting account balance...", total=None)
|
|
941
|
+
bal = nc.users.get_balances()
|
|
942
|
+
|
|
943
|
+
if config.output_format == "table":
|
|
944
|
+
table = Table(title="Account Balance")
|
|
945
|
+
table.add_column("Field", style="cyan")
|
|
946
|
+
table.add_column("Amount", style="green", justify="right")
|
|
947
|
+
|
|
948
|
+
table.add_row(
|
|
949
|
+
"Available Balance", f"{bal.available_balance} {bal.currency}"
|
|
950
|
+
)
|
|
951
|
+
table.add_row("Account Balance", f"{bal.account_balance} {bal.currency}")
|
|
952
|
+
table.add_row("Earned Amount", f"{bal.earned_amount} {bal.currency}")
|
|
953
|
+
table.add_row("Withdrawable", f"{bal.withdrawable_amount} {bal.currency}")
|
|
954
|
+
if bal.funds_required_for_auto_renew > 0:
|
|
955
|
+
table.add_row(
|
|
956
|
+
"Auto-Renew Required",
|
|
957
|
+
f"{bal.funds_required_for_auto_renew} {bal.currency}",
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
console.print(table)
|
|
961
|
+
else:
|
|
962
|
+
output_formatter(bal.model_dump(mode="json"), config.output_format)
|
|
927
963
|
|
|
928
964
|
except NamecheapError as e:
|
|
929
965
|
console.print(f"[red]❌ Error: {e}[/red]")
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
# Development Guide
|
|
2
|
-
|
|
3
|
-
## 🛠️ Development Setup
|
|
4
|
-
|
|
5
|
-
### Prerequisites
|
|
6
|
-
|
|
7
|
-
- Python 3.12+
|
|
8
|
-
- [uv](https://docs.astral.sh/uv/) package manager
|
|
9
|
-
- Git
|
|
10
|
-
|
|
11
|
-
### Setup Development Environment
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
# Clone repository
|
|
15
|
-
git clone https://github.com/adriangalilea/namecheap-python.git
|
|
16
|
-
cd namecheap-python
|
|
17
|
-
|
|
18
|
-
# Install with uv (includes all extras and dev dependencies)
|
|
19
|
-
uv sync --all-extras
|
|
20
|
-
|
|
21
|
-
# Install pre-commit hooks
|
|
22
|
-
pre-commit install
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## 🧪 Running Tests
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
# Run all tests
|
|
29
|
-
uv run pytest
|
|
30
|
-
|
|
31
|
-
# Run with coverage
|
|
32
|
-
uv run pytest --cov=namecheap --cov-report=term-missing
|
|
33
|
-
|
|
34
|
-
# Run specific test file
|
|
35
|
-
uv run pytest tests/test_domains.py
|
|
36
|
-
|
|
37
|
-
# Run with verbose output
|
|
38
|
-
uv run pytest -vv
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## 🔍 Code Quality
|
|
42
|
-
|
|
43
|
-
### Linting and Formatting
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
# Check code with ruff
|
|
47
|
-
uv run ruff check
|
|
48
|
-
|
|
49
|
-
# Fix auto-fixable issues
|
|
50
|
-
uv run ruff check --fix
|
|
51
|
-
|
|
52
|
-
# Format code
|
|
53
|
-
uv run ruff format
|
|
54
|
-
|
|
55
|
-
# Check formatting without changing files
|
|
56
|
-
uv run ruff format --check
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Type Checking
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
# Type check the main package
|
|
63
|
-
uv run mypy src/namecheap
|
|
64
|
-
|
|
65
|
-
# Type check everything (slower)
|
|
66
|
-
uv run mypy src/
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### Pre-commit Hooks
|
|
70
|
-
|
|
71
|
-
The project uses pre-commit hooks to ensure code quality:
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
# Run all pre-commit hooks manually
|
|
75
|
-
pre-commit run --all-files
|
|
76
|
-
|
|
77
|
-
# Update pre-commit hooks
|
|
78
|
-
pre-commit autoupdate
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## 📦 Building and Publishing
|
|
82
|
-
|
|
83
|
-
### Building Locally
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
# Build distribution packages
|
|
87
|
-
uv build
|
|
88
|
-
|
|
89
|
-
# This creates:
|
|
90
|
-
# - dist/namecheap-1.0.0-py3-none-any.whl
|
|
91
|
-
# - dist/namecheap-1.0.0.tar.gz
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Release Process
|
|
95
|
-
|
|
96
|
-
Releases are automated via GitHub Actions:
|
|
97
|
-
|
|
98
|
-
1. **Manual Release** (via GitHub UI):
|
|
99
|
-
- Go to Actions → "Bump Version and Release"
|
|
100
|
-
- Click "Run workflow"
|
|
101
|
-
- Select version bump type (major/minor/patch)
|
|
102
|
-
- This will:
|
|
103
|
-
- Bump version in pyproject.toml
|
|
104
|
-
- Create git tag
|
|
105
|
-
- Build and publish to PyPI
|
|
106
|
-
- Create GitHub release
|
|
107
|
-
|
|
108
|
-
2. **Tag-based Release**:
|
|
109
|
-
```bash
|
|
110
|
-
# Create and push a tag
|
|
111
|
-
git tag v1.0.0
|
|
112
|
-
git push origin v1.0.0
|
|
113
|
-
```
|
|
114
|
-
This triggers the publish workflow automatically.
|
|
115
|
-
|
|
116
|
-
## 🏗️ Project Structure
|
|
117
|
-
|
|
118
|
-
```
|
|
119
|
-
namecheap-python/
|
|
120
|
-
├── src/
|
|
121
|
-
│ ├── namecheap/ # Core SDK package
|
|
122
|
-
│ │ ├── __init__.py
|
|
123
|
-
│ │ ├── client.py # Main client class
|
|
124
|
-
│ │ ├── models.py # Pydantic models
|
|
125
|
-
│ │ ├── errors.py # Custom exceptions
|
|
126
|
-
│ │ └── _api/ # API implementations
|
|
127
|
-
│ │ ├── base.py
|
|
128
|
-
│ │ ├── domains.py
|
|
129
|
-
│ │ └── dns.py
|
|
130
|
-
│ ├── namecheap_cli/ # CLI tool
|
|
131
|
-
│ │ ├── __init__.py
|
|
132
|
-
│ │ ├── __main__.py # CLI entry point
|
|
133
|
-
│ │ └── completion.py # Shell completions
|
|
134
|
-
│ └── namecheap_dns_tui/ # TUI application
|
|
135
|
-
│ ├── __init__.py
|
|
136
|
-
│ └── __main__.py # TUI entry point
|
|
137
|
-
├── tests/ # Test suite
|
|
138
|
-
├── examples/ # Usage examples
|
|
139
|
-
└── docs/ # Documentation
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## 🧩 Adding New Features
|
|
143
|
-
|
|
144
|
-
### Adding a New API Endpoint
|
|
145
|
-
|
|
146
|
-
1. Create model in `src/namecheap/models.py`:
|
|
147
|
-
```python
|
|
148
|
-
class NewFeature(BaseModel):
|
|
149
|
-
field: str
|
|
150
|
-
another_field: int
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
2. Add API method in appropriate file under `src/namecheap/_api/`:
|
|
154
|
-
```python
|
|
155
|
-
def new_feature(self, param: str) -> NewFeature:
|
|
156
|
-
"""Implement new feature."""
|
|
157
|
-
response = self._request("namecheap.newfeature.action", {"Param": param})
|
|
158
|
-
return NewFeature(**response)
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
3. Add tests in `tests/test_newfeature.py`
|
|
162
|
-
|
|
163
|
-
4. Update documentation
|
|
164
|
-
|
|
165
|
-
### Adding CLI Commands
|
|
166
|
-
|
|
167
|
-
1. Add command group or command in `src/namecheap_cli/__main__.py`:
|
|
168
|
-
```python
|
|
169
|
-
@cli.group("newfeature")
|
|
170
|
-
def newfeature_group():
|
|
171
|
-
"""New feature commands."""
|
|
172
|
-
pass
|
|
173
|
-
|
|
174
|
-
@newfeature_group.command("action")
|
|
175
|
-
@click.argument("param")
|
|
176
|
-
@pass_config
|
|
177
|
-
def newfeature_action(config: Config, param: str):
|
|
178
|
-
"""Perform new feature action."""
|
|
179
|
-
nc = config.init_client()
|
|
180
|
-
result = nc.newfeature.action(param)
|
|
181
|
-
# Handle output
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
2. Add tests and update CLI documentation
|
|
185
|
-
|
|
186
|
-
## 🐛 Debugging
|
|
187
|
-
|
|
188
|
-
### Enable Debug Logging
|
|
189
|
-
|
|
190
|
-
```python
|
|
191
|
-
import logging
|
|
192
|
-
logging.basicConfig(level=logging.DEBUG)
|
|
193
|
-
|
|
194
|
-
# Or set environment variable
|
|
195
|
-
export NAMECHEAP_DEBUG=true
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Common Issues
|
|
199
|
-
|
|
200
|
-
1. **Import errors**: Make sure you're using `uv run` or have activated the virtual environment
|
|
201
|
-
2. **API errors**: Check credentials and sandbox mode settings
|
|
202
|
-
3. **Type errors**: Run `mypy` to catch type issues early
|
|
203
|
-
|
|
204
|
-
## 📋 Checklist for PRs
|
|
205
|
-
|
|
206
|
-
- [ ] Code follows project style (run `uv run ruff check`)
|
|
207
|
-
- [ ] All tests pass (`uv run pytest`)
|
|
208
|
-
- [ ] Type hints added (`uv run mypy src/namecheap`)
|
|
209
|
-
- [ ] Documentation updated if needed
|
|
210
|
-
- [ ] Examples work correctly
|
|
211
|
-
- [ ] Commit messages are clear and descriptive
|
|
212
|
-
|
|
213
|
-
## 🔗 Useful Links
|
|
214
|
-
|
|
215
|
-
- [Namecheap API Documentation](https://www.namecheap.com/support/api/methods/)
|
|
216
|
-
- [Python Type Hints](https://docs.python.org/3/library/typing.html)
|
|
217
|
-
- [Pydantic Documentation](https://docs.pydantic.dev/)
|
|
218
|
-
- [Click Documentation](https://click.palletsprojects.com/)
|
|
219
|
-
- [Textual Documentation](https://textual.textualize.io/)
|
|
220
|
-
- [Ruff Documentation](https://docs.astral.sh/ruff/)
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# Pending Features from Previous SDK
|
|
2
|
-
|
|
3
|
-
Based on the previous Namecheap Python SDK implementation, here's what's still pending in our new clean architecture:
|
|
4
|
-
|
|
5
|
-
## 🚧 Not Yet Implemented
|
|
6
|
-
|
|
7
|
-
### 1. **SSL API** (`namecheap.ssl.*`)
|
|
8
|
-
- `create()` - Purchase SSL certificates
|
|
9
|
-
- `activate()` - Activate SSL certificates
|
|
10
|
-
- `getInfo()` - Get SSL certificate details
|
|
11
|
-
- `parseCSR()` - Parse Certificate Signing Request
|
|
12
|
-
- `getApproverEmailList()` - Get domain control validation emails
|
|
13
|
-
- `resendApproverEmail()` - Resend DCV email
|
|
14
|
-
- `reissue()` - Reissue SSL certificate
|
|
15
|
-
- `renew()` - Renew SSL certificate
|
|
16
|
-
- `revoke()` - Revoke SSL certificate
|
|
17
|
-
- `getList()` - List SSL certificates
|
|
18
|
-
|
|
19
|
-
### 2. **Users API** (`namecheap.users.*`)
|
|
20
|
-
- `getBalances()` - Get account balance
|
|
21
|
-
- `changePassword()` - Change account password
|
|
22
|
-
- `update()` - Update user information
|
|
23
|
-
- `createAddFundsRequest()` - Create add funds request
|
|
24
|
-
- `getAddFundsStatus()` - Check add funds status
|
|
25
|
-
- `create()` - Create sub-account (resellers)
|
|
26
|
-
- `login()` - Login to get session
|
|
27
|
-
- `resetPassword()` - Reset password
|
|
28
|
-
|
|
29
|
-
### 3. **Domains Transfer API** (`namecheap.domains.transfer.*`)
|
|
30
|
-
- `create()` - Initiate domain transfer
|
|
31
|
-
- `getStatus()` - Check transfer status
|
|
32
|
-
- `updateStatus()` - Approve/reject transfer
|
|
33
|
-
- `getList()` - List pending transfers
|
|
34
|
-
|
|
35
|
-
### 4. **Domains NS API** (`namecheap.domains.ns.*`) — Glue Records
|
|
36
|
-
- `create()` - Register a child nameserver (e.g., ns1.yourdomain.com → 1.2.3.4)
|
|
37
|
-
- `delete()` - Delete a child nameserver
|
|
38
|
-
- `getInfo()` - Get child nameserver details
|
|
39
|
-
- `update()` - Update child nameserver IP
|
|
40
|
-
|
|
41
|
-
### 5. **Whois API** (`namecheap.whois.*`)
|
|
42
|
-
- `getWhoisInfo()` - Get WHOIS information
|
|
43
|
-
|
|
44
|
-
### 6. **Domains Forwarding API**
|
|
45
|
-
- `setEmailForwarding()` - Configure email forwarding
|
|
46
|
-
- `getEmailForwarding()` - Get email forwarding settings
|
|
47
|
-
|
|
48
|
-
## ✅ Already Implemented (Core Features)
|
|
49
|
-
|
|
50
|
-
### Domains API
|
|
51
|
-
- ✅ `check()` - Check domain availability
|
|
52
|
-
- ✅ `list()` - List domains in account
|
|
53
|
-
- ✅ `register()` - Register new domain
|
|
54
|
-
- ✅ `renew()` - Renew domain
|
|
55
|
-
- ✅ `setContacts()` - Update contact information
|
|
56
|
-
- ✅ `lock()`/`unlock()` - Domain locking
|
|
57
|
-
|
|
58
|
-
### DNS API
|
|
59
|
-
- ✅ `get()` - Get DNS records
|
|
60
|
-
- ✅ `set()` - Set DNS records (with builder pattern!)
|
|
61
|
-
- ✅ `add()` - Add single record
|
|
62
|
-
- ✅ `delete()` - Delete records
|
|
63
|
-
- ✅ `set_custom_nameservers()` - Switch to custom nameservers (e.g., Route 53)
|
|
64
|
-
- ✅ `set_default_nameservers()` - Reset to Namecheap BasicDNS
|
|
65
|
-
- ✅ `get_nameservers()` - Get current nameserver configuration
|
|
66
|
-
|
|
67
|
-
### Enhanced Features
|
|
68
|
-
- ✅ Smart IP detection and validation
|
|
69
|
-
- ✅ Helpful error messages with IP troubleshooting
|
|
70
|
-
- ✅ Pydantic models with proper type safety
|
|
71
|
-
- ✅ Rich logging with colors
|
|
72
|
-
- ✅ Context manager support
|
|
73
|
-
- ✅ DNS builder pattern
|
|
74
|
-
|
|
75
|
-
## 🔧 Pricing Implementation Status
|
|
76
|
-
|
|
77
|
-
### Current Issues
|
|
78
|
-
- The `getPricing` API call is implemented but needs debugging
|
|
79
|
-
- Price fields are properly modeled but not being populated correctly
|
|
80
|
-
- Need to verify the API path and response structure for pricing
|
|
81
|
-
|
|
82
|
-
### Enhanced Domain Check (from old SDK)
|
|
83
|
-
The old SDK had an enhanced domain check that fetched pricing automatically. We've implemented this with the `include_pricing=True` parameter, but it needs fixing.
|
|
84
|
-
|
|
85
|
-
## 📝 Notes
|
|
86
|
-
|
|
87
|
-
The core domain and DNS functionality is complete and working well. The additional APIs (SSL, transfers, user management) are less commonly used but would make the SDK feature-complete with the Namecheap API.
|
|
88
|
-
|
|
89
|
-
Priority for implementation:
|
|
90
|
-
1. Fix pricing retrieval (high - already partially implemented)
|
|
91
|
-
2. SSL API (medium - common use case)
|
|
92
|
-
3. Transfer API (medium - needed for domain transfers)
|
|
93
|
-
4. Users API pricing methods (medium - already have getPricing)
|
|
94
|
-
5. Other APIs (low - rarely used)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/assets/screenshot1.png
RENAMED
|
File without changes
|
{namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/assets/screenshot2.png
RENAMED
|
File without changes
|
{namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/assets/screenshot3.png
RENAMED
|
File without changes
|
{namecheap_python-1.1.0 → namecheap_python-1.2.0}/src/namecheap_dns_tui/assets/screenshot4.png
RENAMED
|
File without changes
|