Coverage for extension_catalog_model/model.py: 96%
92 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:13 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:13 +0000
1from pydantic import BaseModel, HttpUrl
2from pydantic.functional_validators import field_validator, model_validator
3from packaging.version import Version
5from typing import List, Optional
6import re
8class VersionRange(BaseModel):
9 """
10 A specification of the minimum and maximum versions that an extension supports. Versions should be specified in the form "v[MAJOR].[MINOR].[PATCH]" corresponding to semantic versions, although trailing release candidate qualifiers (eg, "-rc1") are also allowed.
12 :param min: The minimum/lowest version (inclusive) that this extension is known to be compatible with.
13 :param max: The maximum/highest version (inclusive) that this extension is known to be compatible with.
14 :param excludes: Any specific versions within the minimum and maximum range (inclusive) that are not compatible.
15 """
16 min: str = "v0.1.0"
17 max: Optional[str] = None
18 excludes: Optional[List[str]] = None
20 @field_validator("min")
21 @classmethod
22 def _validate_min(cls, min):
23 return cls._validate_version(min)
25 @field_validator("max")
26 @classmethod
27 def _validate_min(cls, max):
28 if max is None:
29 return max
30 return cls._validate_version(max)
32 @model_validator(mode="after")
33 def _validate_version_range(self):
34 if (self.max is not None):
35 vmin = Version(self.min)
36 vmax = Version(self.max)
37 assert vmin <= vmax, "Maximum version should be greater than or equal to minimum version."
38 if self.excludes is not None:
39 assert all([Version(x) <= vmax for x in self.excludes]), "All excludes entries should be between the minimum and maximum version."
40 if self.excludes is not None:
41 assert all([vmin <= Version(x) for x in self.excludes]), "All excludes entries should be between the minimum and maximum version."
42 return self
44 @classmethod
45 def _validate_version(cls, version):
46 return _validate_version(version)
49 @field_validator("excludes")
50 def _validate_excludes(cls, excludes):
51 if excludes is None:
52 return excludes
53 return [cls._validate_version(v) for v in excludes]
55def _validate_version(version):
56 assert re.match(r"^v[0-9]+(\.[0-9]+)?(\.[0-9]+)?(-rc[0-9]+)?$", version), "Versions should be specified in the form v[MAJOR].[MINOR].[PATCH] and may include pre-releases, eg v0.6.0-rc1."
57 return version
60class Release(BaseModel):
61 """
62 A description of an extension release hosted on GitHub.
64 :param name: The name of the release. This should be a valid semantic version.
65 :param main_url: The GitHub URL where the main extension jar or zip file can be downloaded.
66 :param required_dependency_urls: SciJava Maven, Maven Central, or GitHub URLs where required dependency jars or zip files can be downloaded.
67 :param optional_dependency_urls: SciJava Maven, Maven Central, or GitHub URLs where optional dependency jars or zip files can be downloaded.
68 :param javadoc_urls: SciJava Maven, Maven Central, or GitHub URLs where javadoc jars or zip files can be downloaded.
69 :param version_range: A specification of minimum and maximum compatible versions.
70 """
71 name: str
72 main_url: HttpUrl
73 required_dependency_urls: Optional[List[HttpUrl]] = None
74 optional_dependency_urls: Optional[List[HttpUrl]] = None
75 javadoc_urls: Optional[List[HttpUrl]] = None
76 version_range: VersionRange
78 @field_validator("name")
79 @classmethod
80 def _validate_name(cls, name):
81 return _validate_version(name)
83 @field_validator("main_url")
84 @classmethod
85 def _check_main_url(cls, main_url: HttpUrl):
86 return _validate_primary_url(main_url)
88 @field_validator("required_dependency_urls",
89 "optional_dependency_urls", "javadoc_urls")
90 @classmethod
91 def _check_urls(cls, urls):
92 if urls is None:
93 return None
94 return [_validate_dependency_url(url) for url in urls]
97class Extension(BaseModel):
98 """
99 A description of an extension.
101 :param name: The extension's name.
102 :param description: A short (one sentence or so) description of what the extension is and what it does.
103 :param author: The author or group responsible for the extension.
104 :param homepage: A link to the GitHub repository associated with the extension.
105 :param starred: Whether the extension is generally useful or recommended for most users.
106 :param releases: A list of available releases of the extension.
107 """
108 name: str
109 description: str
110 author: str
111 homepage: HttpUrl
112 starred: Optional[bool] = False
113 releases: List[Release]
115 @field_validator("homepage")
116 @classmethod
117 def _validate_homepage(cls, url):
118 return _validate_primary_url(url)
119 ## todo: should we check that the download links' owner/repo matches the homepage...?
121class Catalog(BaseModel):
122 """
123 A catalog describing a collection of extensions.
125 :param name: The name of the catalog.
126 :param description: A short (one sentence or so) description of what the catalog contains and what its purpose is.
127 :param extensions: The collection of extensions that the catalog describes.
128 """
129 name: str
130 description: str
131 extensions: List[Extension]
133 @field_validator("extensions")
134 @classmethod
135 def _validate_extension_list(cls, extensions):
136 names = [ext.name for ext in extensions]
137 assert len(names) == len(set(names)), "Duplicated extension names not allowed in extension catalog."
138 return extensions
140def _validate_primary_url(primary_url: HttpUrl):
141 assert primary_url.scheme == "https", "URLs must use https"
142 assert primary_url.host == "github.com", "Homepage and main download links must currently be hosted on github.com."
143 assert re.match("^/[0-9a-zA-Z]+/[0-9a-zA-Z]+/?", primary_url.path) is not None, "Homepage and main download links must currently point to a valid github repo."
144 return primary_url
146def _validate_dependency_url(url):
147 assert url.scheme == "https", "URLs must use https"
148 assert url.host in ["github.com", "maven.scijava.org", "repo1.maven.org"], "Dependency and javadoc download links must currently be hosted on github.com, SciJava Maven, or Maven Central."
149 return url