namecheap-python 1.0.1__tar.gz → 1.0.3__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 (37) hide show
  1. namecheap_python-1.0.3/.github/cliff.toml +34 -0
  2. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/.github/workflows/release.yml +52 -7
  3. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/.pre-commit-config.yaml +0 -12
  4. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/PKG-INFO +1 -7
  5. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/examples/quickstart.py +3 -1
  6. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/pyproject.toml +3 -33
  7. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/_api/base.py +5 -2
  8. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/_api/dns.py +10 -2
  9. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/_api/domains.py +24 -8
  10. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/logging.py +12 -4
  11. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/models.py +5 -3
  12. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_cli/__main__.py +91 -37
  13. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_dns_tui/__main__.py +27 -9
  14. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/uv.lock +236 -232
  15. namecheap_python-1.0.1/.github/workflows/lint.yml +0 -33
  16. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/.env.example +0 -0
  17. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/.gitignore +0 -0
  18. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/CLI.md +0 -0
  19. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/LICENSE +0 -0
  20. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/MANIFEST.in +0 -0
  21. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/README.md +0 -0
  22. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/docs/dev/README.md +0 -0
  23. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/examples/README.md +0 -0
  24. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/pending.md +0 -0
  25. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/__init__.py +0 -0
  26. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/_api/__init__.py +0 -0
  27. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/client.py +0 -0
  28. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap/errors.py +0 -0
  29. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_cli/README.md +0 -0
  30. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_cli/__init__.py +0 -0
  31. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_cli/completion.py +0 -0
  32. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_dns_tui/README.md +0 -0
  33. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_dns_tui/__init__.py +0 -0
  34. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_dns_tui/assets/screenshot1.png +0 -0
  35. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_dns_tui/assets/screenshot2.png +0 -0
  36. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_dns_tui/assets/screenshot3.png +0 -0
  37. {namecheap_python-1.0.1 → namecheap_python-1.0.3}/src/namecheap_dns_tui/assets/screenshot4.png +0 -0
@@ -0,0 +1,34 @@
1
+ [changelog]
2
+ body = """
3
+ {%- macro remote_url() -%}
4
+ https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
5
+ {%- endmacro -%}
6
+
7
+ {% for group, commits in commits | group_by(attribute="group") %}
8
+ ### {{ group | striptags | trim | upper_first }}
9
+ {% for commit in commits %}
10
+ - {% if commit.scope %}*({{ commit.scope }})* {% endif %}{% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }} - ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))
11
+ {%- endfor %}
12
+ {% endfor %}
13
+ """
14
+ trim = true
15
+
16
+ [git]
17
+ conventional_commits = true
18
+ filter_unconventional = true
19
+ protect_breaking_commits = true
20
+ commit_parsers = [
21
+ { message = "^feat", group = "✨ Features" },
22
+ { message = "^fix", group = "🐛 Bug Fixes" },
23
+ { message = "^doc", group = "📚 Documentation" },
24
+ { message = "^perf", group = "⚡ Performance" },
25
+ { message = "^refactor", group = "♻️ Refactor" },
26
+ { message = "^style", group = "🎨 Styling" },
27
+ { message = "^test", group = "🧪 Testing" },
28
+ { message = "^chore\\(release\\): prepare for", skip = true },
29
+ { message = "^chore\\(deps.*\\)", skip = true },
30
+ { message = "^chore", group = "🧹 Miscellaneous Tasks" },
31
+ { message = "^ci|^cd", group = "🏭 CI/CD" },
32
+ { body = ".*security", group = "🛡️ Security" },
33
+ { message = "^revert", group = "⏪ Revert" },
34
+ ]
@@ -1,20 +1,45 @@
1
- name: Release to PyPI
1
+ name: Python CI/CD
2
2
 
3
3
  on:
4
4
  push:
5
5
  branches: [main]
6
+ pull_request:
7
+ branches: [main]
6
8
 
7
9
  jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v5
17
+ with:
18
+ enable-cache: true
19
+
20
+ - name: Set up Python
21
+ run: uv python install 3.12
22
+
23
+ - name: Install dependencies
24
+ run: uv sync --all-extras
25
+
26
+ - name: Run ruff check
27
+ run: uv run ruff check
28
+
29
+ - name: Run ruff format check
30
+ run: uv run ruff format --check
31
+
8
32
  publish:
33
+ needs: lint
34
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
9
35
  runs-on: ubuntu-latest
10
- environment:
11
- name: pypi
12
- url: https://pypi.org/p/namecheap-python
13
36
  permissions:
14
37
  id-token: write
15
38
  contents: write
16
39
  steps:
17
40
  - uses: actions/checkout@v4
41
+ with:
42
+ fetch-depth: 0
18
43
 
19
44
  - name: Set up Python
20
45
  uses: actions/setup-python@v5
@@ -64,10 +89,30 @@ jobs:
64
89
  git tag -a "v$VERSION" -m "Release v$VERSION"
65
90
  git push origin "v$VERSION"
66
91
 
67
- - name: Create GitHub Release
92
+ - name: Check for cliff.toml
68
93
  if: steps.check.outputs.exists == 'false'
94
+ id: check-cliff
95
+ run: |
96
+ if [ -f .github/cliff.toml ]; then
97
+ echo "has_cliff=true" >> $GITHUB_OUTPUT
98
+ else
99
+ echo "has_cliff=false" >> $GITHUB_OUTPUT
100
+ fi
101
+
102
+ - name: Generate Changelog
103
+ if: steps.check.outputs.exists == 'false' && steps.check-cliff.outputs.has_cliff == 'true'
104
+ id: git-cliff
105
+ uses: orhun/git-cliff-action@v4
106
+ with:
107
+ config: .github/cliff.toml
108
+ args: --latest
109
+ env:
110
+ OUTPUT: CHANGELOG.md
111
+ GITHUB_REPO: ${{ github.repository }}
112
+
113
+ - name: Create GitHub Release
114
+ if: steps.check.outputs.exists == 'false' && steps.check-cliff.outputs.has_cliff == 'true'
69
115
  uses: softprops/action-gh-release@v2
70
116
  with:
71
117
  tag_name: v${{ steps.check.outputs.version }}
72
- name: v${{ steps.check.outputs.version }}
73
- generate_release_notes: true
118
+ body: ${{ steps.git-cliff.outputs.content }}
@@ -18,15 +18,3 @@ repos:
18
18
  args: [--fix]
19
19
  - id: ruff-format
20
20
 
21
- - repo: https://github.com/pre-commit/mirrors-mypy
22
- rev: v1.11.0
23
- hooks:
24
- - id: mypy
25
- args: [--strict]
26
- additional_dependencies:
27
- - pydantic>=2.5.0
28
- - httpx>=0.27.0
29
- - types-xmltodict>=0.13.0
30
- - python-dotenv>=1.0.0
31
- - tldextract>=5.0.0
32
- files: ^src/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: namecheap-python
3
- Version: 1.0.1
3
+ Version: 1.0.3
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
@@ -36,13 +36,7 @@ Requires-Dist: platformdirs>=4.0.0; extra == 'cli'
36
36
  Requires-Dist: pyyaml>=6.0.0; extra == 'cli'
37
37
  Requires-Dist: rich>=13.0.0; extra == 'cli'
38
38
  Provides-Extra: dev
39
- Requires-Dist: mypy>=1.11.0; extra == 'dev'
40
- Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
41
- Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
42
- Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
43
- Requires-Dist: pytest>=8.0.0; extra == 'dev'
44
39
  Requires-Dist: ruff>=0.7.0; extra == 'dev'
45
- Requires-Dist: types-xmltodict>=0.13.0; extra == 'dev'
46
40
  Provides-Extra: tui
47
41
  Requires-Dist: textual>=0.47.0; extra == 'tui'
48
42
  Description-Content-Type: text/markdown
@@ -47,7 +47,9 @@ print("🌐 DNS Management example:")
47
47
  try:
48
48
  # Get current DNS records for a domain
49
49
  # Use the first domain from your account, or specify one
50
- domain_name = my_domains[0].name if my_domains else "example.com" # Replace with your domain
50
+ domain_name = (
51
+ my_domains[0].name if my_domains else "example.com"
52
+ ) # Replace with your domain
51
53
  print(f"Current DNS records for {domain_name}:")
52
54
 
53
55
  records = nc.dns.get(domain_name)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "namecheap-python"
3
- version = "1.0.1"
3
+ version = "1.0.3"
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"
@@ -50,13 +50,7 @@ all = [
50
50
  "textual>=0.47.0",
51
51
  ]
52
52
  dev = [
53
- "pytest>=8.0.0",
54
- "pytest-asyncio>=0.23.0",
55
- "pytest-cov>=5.0.0",
56
- "pytest-httpx>=0.30.0",
57
53
  "ruff>=0.7.0",
58
- "mypy>=1.11.0",
59
- "types-xmltodict>=0.13.0",
60
54
  ]
61
55
 
62
56
  [project.scripts]
@@ -72,7 +66,6 @@ packages = ["src/namecheap", "src/namecheap_cli", "src/namecheap_dns_tui"]
72
66
 
73
67
  [tool.ruff]
74
68
  target-version = "py312"
75
- line-length = 100
76
69
  src = ["src"]
77
70
 
78
71
  [tool.ruff.lint]
@@ -88,36 +81,13 @@ select = [
88
81
  "RET", # flake8-return
89
82
  "SIM", # flake8-simplify
90
83
  ]
84
+ # Ignore line length
85
+ extend-ignore = ["E501"]
91
86
  ignore = [
92
87
  "S101", # Use of assert detected - reasonable for tests and validation
93
88
  "SIM110", # Use any() - no current violations, keeping for edge cases
94
89
  "SIM118", # Use `key in dict` instead of `key in dict.keys()` - no current violations
95
90
  ]
96
91
 
97
- [tool.mypy]
98
- python_version = "3.12"
99
- strict = true
100
- pretty = true
101
- show_error_codes = true
102
- show_error_context = true
103
92
 
104
- [tool.pytest.ini_options]
105
- minversion = "8.0"
106
- testpaths = ["tests"]
107
- addopts = "-ra --cov=namecheap --cov-report=term-missing"
108
93
 
109
- [tool.coverage.run]
110
- source = ["src/namecheap"]
111
-
112
- [tool.coverage.report]
113
- exclude_lines = [
114
- "pragma: no cover",
115
- "def __repr__",
116
- "if TYPE_CHECKING:",
117
- "raise NotImplementedError",
118
- ]
119
-
120
- [dependency-groups]
121
- dev = [
122
- "types-pyyaml>=6.0.12.20250516",
123
- ]
@@ -56,7 +56,9 @@ def normalize_xml_response(data: dict[str, Any]) -> dict[str, Any]:
56
56
  # Fix known typos from Namecheap
57
57
  if key == "@YourAdditonalCost": # Their typo (missing 'i' in Additional)
58
58
  normalized["@YourAdditionalCost"] = value # Correct spelling
59
- logger.debug("Fixed Namecheap typo: @YourAdditonalCost -> @YourAdditionalCost")
59
+ logger.debug(
60
+ "Fixed Namecheap typo: @YourAdditonalCost -> @YourAdditionalCost"
61
+ )
60
62
 
61
63
  # Debug canary: Alert if they have both versions (means they're fixing it)
62
64
  if "@YourAdditionalCost" in data and "@YourAdditonalCost" in data:
@@ -70,7 +72,8 @@ def normalize_xml_response(data: dict[str, Any]) -> dict[str, Any]:
70
72
  normalized[key] = normalize_xml_response(value)
71
73
  elif isinstance(value, list):
72
74
  normalized[key] = [
73
- normalize_xml_response(item) if isinstance(item, dict) else item for item in value
75
+ normalize_xml_response(item) if isinstance(item, dict) else item
76
+ for item in value
74
77
  ]
75
78
 
76
79
  return normalized
@@ -35,7 +35,9 @@ class DNSRecordBuilder:
35
35
  Self for chaining
36
36
  """
37
37
  self._records.append(
38
- DNSRecord.model_validate({"@Name": name, "@Type": "A", "@Address": ip, "@TTL": ttl})
38
+ DNSRecord.model_validate(
39
+ {"@Name": name, "@Type": "A", "@Address": ip, "@TTL": ttl}
40
+ )
39
41
  )
40
42
  return self
41
43
 
@@ -94,7 +96,13 @@ class DNSRecordBuilder:
94
96
  """
95
97
  self._records.append(
96
98
  DNSRecord.model_validate(
97
- {"@Name": name, "@Type": "MX", "@Address": server, "@TTL": ttl, "@MXPref": priority}
99
+ {
100
+ "@Name": name,
101
+ "@Type": "MX",
102
+ "@Address": server,
103
+ "@TTL": ttl,
104
+ "@MXPref": priority,
105
+ }
98
106
  )
99
107
  )
100
108
  return self
@@ -17,7 +17,9 @@ from .base import BaseAPI
17
17
  class DomainsAPI(BaseAPI):
18
18
  """Domain management operations."""
19
19
 
20
- def check(self, *domains: str, include_pricing: bool = False) -> builtins.list[DomainCheck]:
20
+ def check(
21
+ self, *domains: str, include_pricing: bool = False
22
+ ) -> builtins.list[DomainCheck]:
21
23
  """
22
24
  Check domain availability.
23
25
 
@@ -158,7 +160,9 @@ class DomainsAPI(BaseAPI):
158
160
 
159
161
  # Add contact info if provided
160
162
  if contact:
161
- contact_data = contact.model_dump() if isinstance(contact, Contact) else contact
163
+ contact_data = (
164
+ contact.model_dump() if isinstance(contact, Contact) else contact
165
+ )
162
166
  # Add contact fields for all types (Registrant, Tech, Admin, AuxBilling)
163
167
  for contact_type in ["Registrant", "Tech", "Admin", "AuxBilling"]:
164
168
  for field, value in contact_data.items():
@@ -284,7 +288,9 @@ class DomainsAPI(BaseAPI):
284
288
  assert isinstance(result, dict)
285
289
  return bool(result)
286
290
 
287
- def _get_pricing(self, domains: builtins.list[str]) -> dict[str, dict[str, Decimal | None]]:
291
+ def _get_pricing(
292
+ self, domains: builtins.list[str]
293
+ ) -> dict[str, dict[str, Decimal | None]]:
288
294
  """
289
295
  Get pricing information for domains.
290
296
 
@@ -362,7 +368,9 @@ class DomainsAPI(BaseAPI):
362
368
  if not isinstance(products, list):
363
369
  products = [products] if products else []
364
370
 
365
- logger.debug(f"Found {len(products)} products in REGISTER category")
371
+ logger.debug(
372
+ f"Found {len(products)} products in REGISTER category"
373
+ )
366
374
 
367
375
  # Find the product matching our TLD
368
376
  for product in products:
@@ -370,7 +378,9 @@ class DomainsAPI(BaseAPI):
370
378
  continue
371
379
 
372
380
  product_name = product.get("@Name", "")
373
- logger.debug(f"Checking product: {product_name} vs {tld}")
381
+ logger.debug(
382
+ f"Checking product: {product_name} vs {tld}"
383
+ )
374
384
 
375
385
  if product_name.lower() == tld.lower():
376
386
  # Get price list
@@ -378,7 +388,9 @@ class DomainsAPI(BaseAPI):
378
388
  if not isinstance(price_info, list):
379
389
  price_info = [price_info] if price_info else []
380
390
 
381
- logger.debug(f"Found {len(price_info)} price entries for {tld}")
391
+ logger.debug(
392
+ f"Found {len(price_info)} price entries for {tld}"
393
+ )
382
394
 
383
395
  # Find 1 year price
384
396
  for price in price_info:
@@ -405,13 +417,17 @@ class DomainsAPI(BaseAPI):
405
417
  # Apply to all domains with this TLD
406
418
  for domain in domain_list:
407
419
  pricing[domain] = {
408
- "regular_price": Decimal(regular_price)
420
+ "regular_price": Decimal(
421
+ regular_price
422
+ )
409
423
  if regular_price
410
424
  else None,
411
425
  "your_price": Decimal(your_price)
412
426
  if your_price
413
427
  else None,
414
- "retail_price": Decimal(retail_price)
428
+ "retail_price": Decimal(
429
+ retail_price
430
+ )
415
431
  if retail_price
416
432
  else None,
417
433
  }
@@ -74,13 +74,21 @@ class ErrorDisplay:
74
74
 
75
75
  if hasattr(error, "_ip_help") and error._ip_help is not None:
76
76
  console.print("\n[yellow]🔍 IP Configuration Issue[/yellow]")
77
- console.print(f" Your current IP: [cyan]{error._ip_help['actual_ip']}[/cyan]")
78
- console.print(f" Configured IP: [cyan]{error._ip_help['configured_ip']}[/cyan]")
77
+ console.print(
78
+ f" Your current IP: [cyan]{error._ip_help['actual_ip']}[/cyan]"
79
+ )
80
+ console.print(
81
+ f" Configured IP: [cyan]{error._ip_help['configured_ip']}[/cyan]"
82
+ )
79
83
  console.print("\n[yellow]💡 To fix this:[/yellow]")
80
- console.print(" 1. Log in to [link=https://www.namecheap.com]Namecheap[/link]")
84
+ console.print(
85
+ " 1. Log in to [link=https://www.namecheap.com]Namecheap[/link]"
86
+ )
81
87
  console.print(" 2. Go to Profile → Tools → API Access")
82
88
  actual_ip = error._ip_help["actual_ip"]
83
- console.print(f" 3. Add this IP to whitelist: [cyan]{actual_ip}[/cyan]")
89
+ console.print(
90
+ f" 3. Add this IP to whitelist: [cyan]{actual_ip}[/cyan]"
91
+ )
84
92
  console.print(
85
93
  f" 4. Update your .env file: [cyan]NAMECHEAP_CLIENT_IP={actual_ip}[/cyan]"
86
94
  )
@@ -89,7 +89,9 @@ class DomainCheck(XMLModel):
89
89
  """
90
90
 
91
91
  domain: str = Field(alias="@Domain", description="Domain name checked")
92
- available: bool = Field(alias="@Available", description="Whether domain is available")
92
+ available: bool = Field(
93
+ alias="@Available", description="Whether domain is available"
94
+ )
93
95
  premium: bool = Field(
94
96
  default=False,
95
97
  alias="@IsPremiumName",
@@ -182,8 +184,8 @@ class DNSRecord(XMLModel):
182
184
  """A DNS record."""
183
185
 
184
186
  name: str = Field(alias="@Name", default="@")
185
- type: Literal["A", "AAAA", "CNAME", "MX", "NS", "TXT", "URL", "URL301", "FRAME"] = Field(
186
- alias="@Type"
187
+ type: Literal["A", "AAAA", "CNAME", "MX", "NS", "TXT", "URL", "URL301", "FRAME"] = (
188
+ Field(alias="@Type")
187
189
  )
188
190
  value: str = Field(alias="@Address")
189
191
  ttl: int = Field(alias="@TTL", default=1800)