Skip to content

Commit 5d7353f

Browse files
committed
chore: shorten code
1 parent 01a647f commit 5d7353f

2 files changed

Lines changed: 57 additions & 67 deletions

File tree

scripts/pyproject.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ authors = [
1111
{ name = "Gregor Sturm", email = "gregor.sturm@scverse.org" },
1212
{ name = "Philipp A.", email = "flying-sheep@web.de" },
1313
]
14-
requires-python = ">=3.12"
14+
requires-python = ">=3.13"
1515
classifiers = [
1616
"Programming Language :: Python :: 3 :: Only",
17-
"Programming Language :: Python :: 3.12",
1817
"Programming Language :: Python :: 3.13",
1918
"Programming Language :: Python :: 3.14",
2019
]
@@ -35,7 +34,6 @@ scripts.register-template-repos = "ecosystem_scripts.template_repo_registry:main
3534
scripts.validate-registry = "ecosystem_scripts.validate_registry:main"
3635

3736
[tool.hatch]
38-
envs.default.python = "3.13"
3937
version.source = "vcs"
4038
version.fallback-version = "0.0"
4139

scripts/src/ecosystem_scripts/validate_registry.py

Lines changed: 56 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import shutil
1111
import sys
1212
from collections import defaultdict
13+
from dataclasses import KW_ONLY, dataclass, field
1314
from importlib.resources import files
1415
from pathlib import Path
1516
from textwrap import dedent
@@ -54,14 +55,19 @@ def append(self, obj: Exception | None) -> None:
5455
)
5556

5657

57-
class LinkChecker:
58-
"""Track known links and validate URLs."""
58+
@dataclass
59+
class HTTPValidator[E = str]:
60+
"""Validate HTTP URLs."""
61+
62+
client: httpx.Client
63+
_: KW_ONLY
64+
validated: set[E] = field(default_factory=set)
5965

60-
def __init__(self, client: httpx.Client) -> None:
61-
self.known_links: set[str] = set()
62-
self.client = client
6366

64-
def check_and_register(self, url: str, context: str) -> None | ValidationError:
67+
class LinkChecker(HTTPValidator):
68+
"""Track known links and validate URLs."""
69+
70+
def __call__(self, url: str, context: str) -> None | ValidationError:
6571
"""Check if URL is duplicate, validate it exists, and register it.
6672
6773
Parameters
@@ -77,7 +83,7 @@ def check_and_register(self, url: str, context: str) -> None | ValidationError:
7783
f"Please use the default version in ReadTheDocs URLs instead of {m['version']!r}:\n{url}\n->\n{new_url}"
7884
)
7985
return ValidationError(msg)
80-
if url in self.known_links:
86+
if url in self.validated:
8187
msg = f"{context}: Duplicate link: {url}"
8288
return ValidationError(msg)
8389

@@ -91,19 +97,17 @@ def check_and_register(self, url: str, context: str) -> None | ValidationError:
9197
msg = f"URL {url} is not reachable (error {response.status_code}). "
9298
return ValidationError(msg)
9399

94-
self.known_links.add(url)
100+
self.validated.add(url)
95101
return None
96102

97103

98-
class GitHubUserValidator:
104+
@dataclass
105+
class GitHubUserValidator(HTTPValidator):
99106
"""Validate GitHub usernames using the GitHub API."""
100107

101-
def __init__(self, client: httpx.Client, github_token: str | None = None) -> None:
102-
self.client = client
103-
self.github_token = github_token
104-
self.validated_users: set[str] = set()
108+
github_token: str | None = None
105109

106-
def validate_usernames(self, usernames: Sequence[str], context: str) -> None | ValidationError:
110+
def __call__(self, usernames: Sequence[str], context: str) -> None | ValidationError:
107111
"""Validate that a GitHub username exists.
108112
109113
Parameters
@@ -114,7 +118,7 @@ def validate_usernames(self, usernames: Sequence[str], context: str) -> None | V
114118
Context information for error messages (e.g., file being validated)
115119
"""
116120

117-
if not (unvalidated := list(set(usernames) - self.validated_users)):
121+
if not (unvalidated := list(set(usernames) - self.validated)):
118122
return None
119123

120124
headers = {}
@@ -141,19 +145,16 @@ def validate_usernames(self, usernames: Sequence[str], context: str) -> None | V
141145
msg = f"{context}: Failed to validate GitHub users {unvalidated!r}:\n{error_msgs}"
142146
return ValidationError(msg)
143147

144-
self.validated_users |= set(unvalidated)
148+
self.validated |= set(unvalidated)
145149
log.info(f"Validated GitHub users: {unvalidated!r}")
146150
return None
147151

148152

149-
class PyPIValidator:
153+
@dataclass
154+
class PyPIValidator(HTTPValidator):
150155
"""Validate PyPI package names against the PyPI API."""
151156

152-
def __init__(self, client: httpx.Client) -> None:
153-
self.client = client
154-
self.validated_packages: set[str] = set()
155-
156-
def validate_package(self, package_name: str, context: str) -> None | ValidationError:
157+
def __call__(self, package_name: str, context: str) -> None | ValidationError:
157158
"""Validate that a PyPI package exists.
158159
159160
Parameters
@@ -163,7 +164,7 @@ def validate_package(self, package_name: str, context: str) -> None | Validation
163164
context
164165
Context information for error messages (e.g., file being validated)
165166
"""
166-
if package_name in self.validated_packages:
167+
if package_name in self.validated:
167168
return None
168169

169170
try:
@@ -179,19 +180,16 @@ def validate_package(self, package_name: str, context: str) -> None | Validation
179180
msg = f"{context}: Failed to validate PyPI package {package_name!r} (error {response.status_code})"
180181
return ValidationError(msg)
181182

182-
self.validated_packages.add(package_name)
183+
self.validated.add(package_name)
183184
log.info(f"Validated PyPI package: {package_name}")
184185
return None
185186

186187

187-
class CondaValidator:
188+
@dataclass
189+
class CondaValidator(HTTPValidator):
188190
"""Validate Conda package identifiers using the Anaconda API."""
189191

190-
def __init__(self, client: httpx.Client) -> None:
191-
self.client = client
192-
self.validated_packages: set[str] = set()
193-
194-
def validate_package(self, package_spec: str, context: str) -> None | ValidationError:
192+
def __call__(self, package_spec: str, context: str) -> None | ValidationError:
195193
"""Validate that a Conda package exists.
196194
197195
Parameters
@@ -201,7 +199,7 @@ def validate_package(self, package_spec: str, context: str) -> None | Validation
201199
context
202200
Context information for error messages (e.g., file being validated)
203201
"""
204-
if package_spec in self.validated_packages:
202+
if package_spec in self.validated:
205203
return None
206204

207205
# Parse channel and package name
@@ -225,19 +223,16 @@ def validate_package(self, package_spec: str, context: str) -> None | Validation
225223
msg = f"{context}: Failed to validate Conda package '{package_spec}' (error {response.status_code})"
226224
return ValidationError(msg)
227225

228-
self.validated_packages.add(package_spec)
226+
self.validated.add(package_spec)
229227
log.info(f"Validated Conda package: {package_spec}")
230228
return None
231229

232230

233-
class CRANValidator:
231+
@dataclass
232+
class CRANValidator(HTTPValidator):
234233
"""Validate CRAN package names using the CRAN API."""
235234

236-
def __init__(self, client: httpx.Client) -> None:
237-
self.client = client
238-
self.validated_packages: set[str] = set()
239-
240-
def validate_package(self, package_name: str, context: str) -> None | ValidationError:
235+
def __call__(self, package_name: str, context: str) -> None | ValidationError:
241236
"""Validate that a CRAN package exists.
242237
243238
Parameters
@@ -247,7 +242,7 @@ def validate_package(self, package_name: str, context: str) -> None | Validation
247242
context
248243
Context information for error messages (e.g., file being validated)
249244
"""
250-
if package_name in self.validated_packages:
245+
if package_name in self.validated:
251246
return None
252247

253248
# CRAN packages can be checked via the packages database
@@ -264,19 +259,16 @@ def validate_package(self, package_name: str, context: str) -> None | Validation
264259
msg = f"{context}: Failed to validate CRAN package '{package_name}' (error {response.status_code})"
265260
return ValidationError(msg)
266261

267-
self.validated_packages.add(package_name)
262+
self.validated.add(package_name)
268263
log.info(f"Validated CRAN package: {package_name}")
269264
return None
270265

271266

272-
class BioconductorValidator:
267+
@dataclass
268+
class BioconductorValidator(HTTPValidator):
273269
"""Validate Bioconductor package names using the Bioconductor API."""
274270

275-
def __init__(self, client: httpx.Client) -> None:
276-
self.client = client
277-
self.validated_packages: set[str] = set()
278-
279-
def validate_package(self, package_name: str, context: str) -> None | ValidationError:
271+
def __call__(self, package_name: str, context: str) -> None | ValidationError:
280272
"""Validate that a Bioconductor package exists.
281273
282274
Parameters
@@ -286,7 +278,7 @@ def validate_package(self, package_name: str, context: str) -> None | Validation
286278
context
287279
Context information for error messages (e.g., file being validated)
288280
"""
289-
if package_name in self.validated_packages:
281+
if package_name in self.validated:
290282
return None
291283

292284
# Bioconductor packages can be checked via their web API
@@ -303,7 +295,7 @@ def validate_package(self, package_name: str, context: str) -> None | Validation
303295
msg = f"{context}: Failed to validate Bioconductor package '{package_name}' (error {response.status_code})"
304296
return ValidationError(msg)
305297

306-
self.validated_packages.add(package_name)
298+
self.validated.add(package_name)
307299
log.info(f"Validated Bioconductor package: {package_name}")
308300
return None
309301

@@ -341,15 +333,15 @@ def validate_packages( # noqa: C901
341333

342334
# using different link checkers,
343335
# because each of them may point to the same URL and this wouldn't qualify as duplicate
344-
link_checker_home = LinkChecker(retry_client)
345-
link_checker_docs = LinkChecker(retry_client)
346-
link_checker_tutorials = LinkChecker(retry_client)
336+
check_home = LinkChecker(retry_client)
337+
check_docs = LinkChecker(retry_client)
338+
check_tutorial = LinkChecker(retry_client)
347339

348-
github_validator = GitHubUserValidator(retry_client, github_token)
349-
pypi_validator = PyPIValidator(retry_client)
350-
conda_validator = CondaValidator(retry_client)
351-
cran_validator = CRANValidator(retry_client)
352-
bioconductor_validator = BioconductorValidator(retry_client)
340+
check_gh_users = GitHubUserValidator(retry_client, github_token)
341+
check_pypi = PyPIValidator(retry_client)
342+
check_conda = CondaValidator(retry_client)
343+
check_cran = CRANValidator(retry_client)
344+
check_bioc = BioconductorValidator(retry_client)
353345

354346
errors: defaultdict[str, ErrorList] = defaultdict(ErrorList)
355347
package_metadata: list[ScverseEcosystemPackages] = []
@@ -367,25 +359,25 @@ def validate_packages( # noqa: C901
367359
pkg_errors.append(e)
368360

369361
# Check and register all links
370-
pkg_errors.append(link_checker_home.check_and_register(tmp_meta["project_home"], pkg_id))
371-
pkg_errors.append(link_checker_docs.check_and_register(tmp_meta["documentation_home"], pkg_id))
362+
pkg_errors.append(check_home(tmp_meta["project_home"], pkg_id))
363+
pkg_errors.append(check_docs(tmp_meta["documentation_home"], pkg_id))
372364
if url := tmp_meta.get("tutorials_home"):
373-
pkg_errors.append(link_checker_tutorials.check_and_register(url, pkg_id))
365+
pkg_errors.append(check_tutorial(url, pkg_id))
374366

375367
# Validate GitHub usernames in contact field
376368
if usernames := tmp_meta.get("contact"):
377-
pkg_errors.append(github_validator.validate_usernames(usernames, pkg_id))
369+
pkg_errors.append(check_gh_users(usernames, pkg_id))
378370

379371
# Validate install packages
380372
if install_info := tmp_meta.get("install"):
381373
if pypi_name := install_info.get("pypi"):
382-
pkg_errors.append(pypi_validator.validate_package(pypi_name, pkg_id))
374+
pkg_errors.append(check_pypi(pypi_name, pkg_id))
383375
if conda_name := install_info.get("conda"):
384-
pkg_errors.append(conda_validator.validate_package(conda_name, pkg_id))
376+
pkg_errors.append(check_conda(conda_name, pkg_id))
385377
if cran_name := install_info.get("cran"):
386-
pkg_errors.append(cran_validator.validate_package(cran_name, pkg_id))
378+
pkg_errors.append(check_cran(cran_name, pkg_id))
387379
if bioconductor_name := install_info.get("bioconductor"):
388-
pkg_errors.append(bioconductor_validator.validate_package(bioconductor_name, pkg_id))
380+
pkg_errors.append(check_bioc(bioconductor_name, pkg_id))
389381

390382
# Check logo (if available) and make path relative to root of registry
391383
if "logo" in tmp_meta:

0 commit comments

Comments
 (0)