namecheap-python 1.0.5__tar.gz → 1.1.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.
Files changed (36) hide show
  1. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/PKG-INFO +24 -2
  2. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/README.md +23 -1
  3. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/pending.md +8 -5
  4. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/pyproject.toml +1 -1
  5. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/__init__.py +3 -2
  6. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/_api/dns.py +114 -1
  7. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/models.py +13 -3
  8. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_cli/__main__.py +125 -0
  9. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/uv.lock +1 -1
  10. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/.env.example +0 -0
  11. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/.github/cliff.toml +0 -0
  12. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/.github/workflows/release.yml +0 -0
  13. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/.gitignore +0 -0
  14. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/.pre-commit-config.yaml +0 -0
  15. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/CLI.md +0 -0
  16. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/LICENSE +0 -0
  17. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/MANIFEST.in +0 -0
  18. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/docs/dev/README.md +0 -0
  19. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/examples/README.md +0 -0
  20. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/examples/quickstart.py +0 -0
  21. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/_api/__init__.py +0 -0
  22. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/_api/base.py +0 -0
  23. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/_api/domains.py +0 -0
  24. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/client.py +0 -0
  25. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/errors.py +0 -0
  26. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap/logging.py +0 -0
  27. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_cli/README.md +0 -0
  28. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_cli/__init__.py +0 -0
  29. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_cli/completion.py +0 -0
  30. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_dns_tui/README.md +0 -0
  31. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_dns_tui/__init__.py +0 -0
  32. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_dns_tui/__main__.py +0 -0
  33. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_dns_tui/assets/screenshot1.png +0 -0
  34. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_dns_tui/assets/screenshot2.png +0 -0
  35. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_dns_tui/assets/screenshot3.png +0 -0
  36. {namecheap_python-1.0.5 → namecheap_python-1.1.0}/src/namecheap_dns_tui/assets/screenshot4.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: namecheap-python
3
- Version: 1.0.5
3
+ Version: 1.1.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
@@ -302,6 +302,24 @@ nc.dns.set("example.com",
302
302
 
303
303
  **Note on TTL:** The default TTL is **1799 seconds**, which displays as **"Automatic"** in the Namecheap web interface. This is an undocumented Namecheap API behavior. You can specify custom TTL values (60-86400 seconds) in any DNS method.
304
304
 
305
+ ### Nameserver Management
306
+
307
+ ```python
308
+ # Check current nameservers
309
+ ns = nc.dns.get_nameservers("example.com")
310
+ print(ns.nameservers) # ['dns1.registrar-servers.com', 'dns2.registrar-servers.com']
311
+ print(ns.is_default) # True
312
+
313
+ # Switch to custom nameservers (e.g., Cloudflare, Route 53)
314
+ nc.dns.set_custom_nameservers("example.com", [
315
+ "ns1.cloudflare.com",
316
+ "ns2.cloudflare.com",
317
+ ])
318
+
319
+ # Reset back to Namecheap BasicDNS
320
+ nc.dns.set_default_nameservers("example.com")
321
+ ```
322
+
305
323
  ### Domain Management
306
324
 
307
325
  ```python
@@ -381,7 +399,7 @@ The following Namecheap API features are planned for future releases:
381
399
 
382
400
  - **SSL API** - Certificate management
383
401
  - **Domain Transfer API** - Transfer domains between registrars
384
- - **Domain NS API** - Custom nameserver management
402
+ - **Domain NS API** - Glue record management (child nameservers)
385
403
  - **Users API** - Account management and balance checking
386
404
  - **Whois API** - WHOIS information lookups
387
405
  - **Email Forwarding** - Email forwarding configuration
@@ -399,3 +417,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
399
417
  ## 🤝 Contributing
400
418
 
401
419
  Contributions are welcome! Please feel free to submit a Pull Request. See the [Development Guide](docs/dev/README.md) for setup instructions and guidelines.
420
+
421
+ ### Contributors
422
+
423
+ - [@cosmin](https://github.com/cosmin) — Nameserver management
@@ -260,6 +260,24 @@ nc.dns.set("example.com",
260
260
 
261
261
  **Note on TTL:** The default TTL is **1799 seconds**, which displays as **"Automatic"** in the Namecheap web interface. This is an undocumented Namecheap API behavior. You can specify custom TTL values (60-86400 seconds) in any DNS method.
262
262
 
263
+ ### Nameserver Management
264
+
265
+ ```python
266
+ # Check current nameservers
267
+ ns = nc.dns.get_nameservers("example.com")
268
+ print(ns.nameservers) # ['dns1.registrar-servers.com', 'dns2.registrar-servers.com']
269
+ print(ns.is_default) # True
270
+
271
+ # Switch to custom nameservers (e.g., Cloudflare, Route 53)
272
+ nc.dns.set_custom_nameservers("example.com", [
273
+ "ns1.cloudflare.com",
274
+ "ns2.cloudflare.com",
275
+ ])
276
+
277
+ # Reset back to Namecheap BasicDNS
278
+ nc.dns.set_default_nameservers("example.com")
279
+ ```
280
+
263
281
  ### Domain Management
264
282
 
265
283
  ```python
@@ -339,7 +357,7 @@ The following Namecheap API features are planned for future releases:
339
357
 
340
358
  - **SSL API** - Certificate management
341
359
  - **Domain Transfer API** - Transfer domains between registrars
342
- - **Domain NS API** - Custom nameserver management
360
+ - **Domain NS API** - Glue record management (child nameservers)
343
361
  - **Users API** - Account management and balance checking
344
362
  - **Whois API** - WHOIS information lookups
345
363
  - **Email Forwarding** - Email forwarding configuration
@@ -357,3 +375,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
357
375
  ## 🤝 Contributing
358
376
 
359
377
  Contributions are welcome! Please feel free to submit a Pull Request. See the [Development Guide](docs/dev/README.md) for setup instructions and guidelines.
378
+
379
+ ### Contributors
380
+
381
+ - [@cosmin](https://github.com/cosmin) — Nameserver management
@@ -32,11 +32,11 @@ Based on the previous Namecheap Python SDK implementation, here's what's still p
32
32
  - `updateStatus()` - Approve/reject transfer
33
33
  - `getList()` - List pending transfers
34
34
 
35
- ### 4. **Domains NS API** (`namecheap.domains.ns.*`)
36
- - `create()` - Create nameserver
37
- - `delete()` - Delete nameserver
38
- - `getInfo()` - Get nameserver details
39
- - `update()` - Update nameserver IP
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
40
 
41
41
  ### 5. **Whois API** (`namecheap.whois.*`)
42
42
  - `getWhoisInfo()` - Get WHOIS information
@@ -60,6 +60,9 @@ Based on the previous Namecheap Python SDK implementation, here's what's still p
60
60
  - ✅ `set()` - Set DNS records (with builder pattern!)
61
61
  - ✅ `add()` - Add single record
62
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
63
66
 
64
67
  ### Enhanced Features
65
68
  - ✅ Smart IP detection and validation
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "namecheap-python"
3
- version = "1.0.5"
3
+ version = "1.1.0"
4
4
  description = "A friendly Python SDK for Namecheap API"
5
5
  authors = [{name = "Adrian Galilea Delgado", email = "adriangalilea@gmail.com"}]
6
6
  readme = "README.md"
@@ -12,9 +12,9 @@ from __future__ import annotations
12
12
 
13
13
  from .client import Namecheap
14
14
  from .errors import ConfigurationError, NamecheapError, ValidationError
15
- from .models import Contact, DNSRecord, Domain, DomainCheck
15
+ from .models import Contact, DNSRecord, Domain, DomainCheck, Nameservers
16
16
 
17
- __version__ = "1.0.5"
17
+ __version__ = "1.1.0"
18
18
  __all__ = [
19
19
  "ConfigurationError",
20
20
  "Contact",
@@ -23,5 +23,6 @@ __all__ = [
23
23
  "DomainCheck",
24
24
  "Namecheap",
25
25
  "NamecheapError",
26
+ "Nameservers",
26
27
  "ValidationError",
27
28
  ]
@@ -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
9
+ from namecheap.models import DNSRecord, Nameservers
10
10
 
11
11
  from .base import BaseAPI
12
12
 
@@ -381,3 +381,116 @@ class DnsAPI(BaseAPI):
381
381
  >>> nc.dns.set("example.com", builder)
382
382
  """
383
383
  return DNSRecordBuilder()
384
+
385
+ def set_custom_nameservers(self, domain: str, nameservers: list[str]) -> bool:
386
+ """
387
+ Set custom nameservers for a domain.
388
+
389
+ This switches the domain from Namecheap's default DNS to custom
390
+ nameservers (e.g., Route 53, Cloudflare, etc.).
391
+
392
+ Args:
393
+ domain: Domain name
394
+ nameservers: List of nameserver hostnames (e.g., ["ns1.example.com", "ns2.example.com"])
395
+
396
+ Returns:
397
+ True if successful
398
+
399
+ Examples:
400
+ >>> nc.dns.set_custom_nameservers("example.com", [
401
+ ... "ns-123.awsdns-45.com",
402
+ ... "ns-456.awsdns-67.net",
403
+ ... "ns-789.awsdns-89.org",
404
+ ... "ns-012.awsdns-12.co.uk",
405
+ ... ])
406
+ """
407
+ assert nameservers, "At least one nameserver is required"
408
+ assert len(nameservers) <= 5, "Maximum of 5 nameservers allowed"
409
+
410
+ ext = tldextract.extract(domain)
411
+ if not ext.domain or not ext.suffix:
412
+ raise ValueError(f"Invalid domain name: {domain}")
413
+
414
+ result: Any = self._request(
415
+ "namecheap.domains.dns.setCustom",
416
+ {
417
+ "SLD": ext.domain,
418
+ "TLD": ext.suffix,
419
+ "Nameservers": ",".join(nameservers),
420
+ },
421
+ path="DomainDNSSetCustomResult",
422
+ )
423
+
424
+ return bool(result and result.get("@Updated") == "true")
425
+
426
+ def set_default_nameservers(self, domain: str) -> bool:
427
+ """
428
+ Reset domain to use Namecheap's default nameservers.
429
+
430
+ This switches the domain back to Namecheap BasicDNS from custom nameservers.
431
+
432
+ Args:
433
+ domain: Domain name
434
+
435
+ Returns:
436
+ True if successful
437
+
438
+ Examples:
439
+ >>> nc.dns.set_default_nameservers("example.com")
440
+ """
441
+ ext = tldextract.extract(domain)
442
+ if not ext.domain or not ext.suffix:
443
+ raise ValueError(f"Invalid domain name: {domain}")
444
+
445
+ result: Any = self._request(
446
+ "namecheap.domains.dns.setDefault",
447
+ {
448
+ "SLD": ext.domain,
449
+ "TLD": ext.suffix,
450
+ },
451
+ path="DomainDNSSetDefaultResult",
452
+ )
453
+
454
+ return bool(result and result.get("@Updated") == "true")
455
+
456
+ def get_nameservers(self, domain: str) -> Nameservers:
457
+ """
458
+ Get current nameservers for a domain.
459
+
460
+ Args:
461
+ domain: Domain name
462
+
463
+ Returns:
464
+ Nameservers with is_default flag and nameserver hostnames
465
+
466
+ Examples:
467
+ >>> ns = nc.dns.get_nameservers("example.com")
468
+ >>> ns.is_default
469
+ True
470
+ >>> ns.nameservers
471
+ ['dns1.registrar-servers.com', 'dns2.registrar-servers.com']
472
+ """
473
+ ext = tldextract.extract(domain)
474
+ if not ext.domain or not ext.suffix:
475
+ raise ValueError(f"Invalid domain name: {domain}")
476
+
477
+ result: Any = self._request(
478
+ "namecheap.domains.dns.getList",
479
+ {
480
+ "SLD": ext.domain,
481
+ "TLD": ext.suffix,
482
+ },
483
+ path="DomainDNSGetListResult",
484
+ )
485
+
486
+ assert result, f"API returned empty result for {domain} nameserver query"
487
+
488
+ is_default = result.get("@IsUsingOurDNS", "false").lower() == "true"
489
+
490
+ ns_data = result.get("Nameserver", [])
491
+ assert isinstance(ns_data, str | list), (
492
+ f"Unexpected Nameserver type: {type(ns_data)}"
493
+ )
494
+ nameservers = [ns_data] if isinstance(ns_data, str) else ns_data
495
+
496
+ return Nameservers(is_default=is_default, nameservers=nameservers)
@@ -239,15 +239,25 @@ class Domain(XMLModel):
239
239
  @field_validator("created", "expires", mode="before")
240
240
  @classmethod
241
241
  def parse_datetime(cls, v: Any) -> datetime:
242
- """Parse datetime strings."""
242
+ """Parse datetime from Namecheap API or Pydantic serialization."""
243
243
  if isinstance(v, datetime):
244
244
  return v
245
245
  if isinstance(v, str):
246
- # Namecheap uses MM/DD/YYYY format
247
- return datetime.strptime(v, "%m/%d/%Y")
246
+ # Namecheap API format (MM/DD/YYYY)
247
+ if "/" in v:
248
+ return datetime.strptime(v, "%m/%d/%Y")
249
+ # Pydantic serialization format (ISO 8601)
250
+ return datetime.fromisoformat(v)
248
251
  raise ValueError(f"Cannot parse datetime from {v}")
249
252
 
250
253
 
254
+ class Nameservers(BaseModel):
255
+ """Current nameserver configuration for a domain."""
256
+
257
+ is_default: bool = Field(description="True when using Namecheap's own DNS")
258
+ nameservers: list[str] = Field(description="Nameserver hostnames")
259
+
260
+
251
261
  class Contact(BaseModel):
252
262
  """Contact information for domain registration."""
253
263
 
@@ -700,6 +700,131 @@ def dns_delete(
700
700
  sys.exit(1)
701
701
 
702
702
 
703
+ @dns_group.command("nameservers")
704
+ @click.argument("domain")
705
+ @pass_config
706
+ def dns_nameservers(config: Config, domain: str) -> None:
707
+ """Show current nameserver configuration for a domain."""
708
+ nc = config.init_client()
709
+
710
+ try:
711
+ with Progress(
712
+ SpinnerColumn(),
713
+ TextColumn("[progress.description]{task.description}"),
714
+ transient=True,
715
+ ) as progress:
716
+ progress.add_task(f"Getting nameserver info for {domain}...", total=None)
717
+ ns = nc.dns.get_nameservers(domain)
718
+
719
+ if config.output_format == "table":
720
+ console.print(f"\n[bold cyan]Nameservers for {domain}[/bold cyan]\n")
721
+
722
+ if ns.is_default:
723
+ console.print("[green]Using Namecheap BasicDNS[/green]")
724
+ else:
725
+ console.print("[yellow]Using custom nameservers:[/yellow]")
726
+ for nameserver in ns.nameservers:
727
+ console.print(f" • {nameserver}")
728
+ else:
729
+ output_formatter(ns.model_dump(), config.output_format)
730
+
731
+ except NamecheapError as e:
732
+ console.print(f"[red]❌ Error: {e}[/red]")
733
+ sys.exit(1)
734
+
735
+
736
+ @dns_group.command("set-nameservers")
737
+ @click.argument("domain")
738
+ @click.argument("nameservers", nargs=-1, required=True)
739
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
740
+ @pass_config
741
+ def dns_set_nameservers(
742
+ config: Config, domain: str, nameservers: tuple[str, ...], yes: bool
743
+ ) -> None:
744
+ """Set custom nameservers for a domain.
745
+
746
+ This switches the domain from Namecheap's default DNS to custom nameservers.
747
+
748
+ Example:
749
+ namecheap-cli dns set-nameservers example.com ns1.route53.com ns2.route53.com
750
+ """
751
+ nc = config.init_client()
752
+
753
+ try:
754
+ if not yes and not config.quiet:
755
+ console.print(
756
+ f"\n[yellow]Setting custom nameservers for {domain}:[/yellow]"
757
+ )
758
+ for ns in nameservers:
759
+ console.print(f" • {ns}")
760
+ console.print()
761
+
762
+ if not Confirm.ask("Continue?", default=True):
763
+ console.print("[yellow]Cancelled[/yellow]")
764
+ return
765
+
766
+ with Progress(
767
+ SpinnerColumn(),
768
+ TextColumn("[progress.description]{task.description}"),
769
+ transient=True,
770
+ ) as progress:
771
+ progress.add_task(f"Setting nameservers for {domain}...", total=None)
772
+ success = nc.dns.set_custom_nameservers(domain, list(nameservers))
773
+
774
+ if success:
775
+ console.print(f"[green]✅ Custom nameservers set for {domain}[/green]")
776
+ if not config.quiet:
777
+ console.print(
778
+ "\n[dim]Note: DNS propagation may take up to 48 hours.[/dim]"
779
+ )
780
+ else:
781
+ console.print("[red]❌ Failed to set nameservers[/red]")
782
+ sys.exit(1)
783
+
784
+ except NamecheapError as e:
785
+ console.print(f"[red]❌ Error: {e}[/red]")
786
+ sys.exit(1)
787
+
788
+
789
+ @dns_group.command("reset-nameservers")
790
+ @click.argument("domain")
791
+ @click.option("--yes", "-y", is_flag=True, help="Skip confirmation")
792
+ @pass_config
793
+ def dns_reset_nameservers(config: Config, domain: str, yes: bool) -> None:
794
+ """Reset domain to use Namecheap's default nameservers.
795
+
796
+ This switches the domain back to Namecheap BasicDNS from custom nameservers.
797
+ """
798
+ nc = config.init_client()
799
+
800
+ try:
801
+ if not yes and not config.quiet:
802
+ console.print(
803
+ f"\n[yellow]This will reset {domain} to Namecheap's default DNS.[/yellow]"
804
+ )
805
+ if not Confirm.ask("Continue?", default=True):
806
+ console.print("[yellow]Cancelled[/yellow]")
807
+ return
808
+
809
+ with Progress(
810
+ SpinnerColumn(),
811
+ TextColumn("[progress.description]{task.description}"),
812
+ transient=True,
813
+ ) as progress:
814
+ progress.add_task(f"Resetting nameservers for {domain}...", total=None)
815
+ success = nc.dns.set_default_nameservers(domain)
816
+
817
+ if success:
818
+ console.print(f"[green]✅ {domain} is now using Namecheap BasicDNS[/green]")
819
+ else:
820
+ console.print("[red]❌ Failed to reset nameservers[/red]")
821
+ sys.exit(1)
822
+
823
+ except NamecheapError as e:
824
+ console.print(f"[red]❌ Error: {e}[/red]")
825
+ sys.exit(1)
826
+
827
+
703
828
  @dns_group.command("export")
704
829
  @click.argument("domain")
705
830
  @click.option(
@@ -200,7 +200,7 @@ wheels = [
200
200
 
201
201
  [[package]]
202
202
  name = "namecheap-python"
203
- version = "1.0.5"
203
+ version = "1.1.0"
204
204
  source = { editable = "." }
205
205
  dependencies = [
206
206
  { name = "httpx" },