Coverage for src/bioimageio/spec/_internal/root_url.py: 68%
73 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 19:19 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 19:19 +0000
1from __future__ import annotations
3from abc import ABC
4from pathlib import PurePosixPath
5from typing import Any, ClassVar, Generic, Iterable, TypeVar
6from urllib.parse import urlsplit, urlunsplit
8import pydantic
9from pydantic import RootModel
11from .validated_string import ValidatedString
13ValidatedType = TypeVar("ValidatedType", pydantic.HttpUrl, pydantic.FtpUrl)
16class _RootUrl(ValidatedString, ABC, Generic[ValidatedType]):
17 """An untested HTTP URL, possibly a 'URL folder' or an invalid HTTP URL"""
19 root_model: ClassVar[type[RootModel[Any]]]
20 _validated: ValidatedType
22 def absolute(self):
23 """analog to `absolute` method of pathlib."""
24 return self
26 @property
27 def scheme(self) -> str:
28 return self._validated.scheme
30 @property
31 def host(self) -> str | None:
32 return self._validated.host
34 @property
35 def path(self) -> str | None:
36 return self._validated.path
38 @property
39 def suffix(self) -> str:
40 if self.path is None:
41 return ""
42 else:
43 return PurePosixPath(self.path).suffix
45 @property
46 def suffixes(self) -> list[str]:
47 if self.path is None:
48 return []
49 else:
50 return PurePosixPath(self.path).suffixes
52 def __truediv__(self, other: str) -> RootHttpUrl:
53 parsed = urlsplit(str(self))
54 return RootHttpUrl(
55 urlunsplit(
56 (
57 parsed.scheme,
58 parsed.netloc,
59 f"{parsed.path.strip('/')}/{other.strip('/')}",
60 parsed.query,
61 parsed.fragment,
62 )
63 )
64 )
67class RootHttpUrl(_RootUrl[pydantic.HttpUrl]):
68 """An untested HTTP URL, possibly a 'URL folder'"""
70 root_model: ClassVar[type[RootModel[Any]]] = RootModel[pydantic.HttpUrl]
71 _validated: pydantic.HttpUrl
73 @property
74 def parent(self) -> RootHttpUrl:
75 parsed = urlsplit(str(self))
76 path = list(parsed.path.split("/"))
77 if (
78 parsed.netloc == "zenodo.org"
79 and parsed.path.startswith("/api/records/")
80 and parsed.path.endswith("/content")
81 ):
82 path[-2:-1] = []
83 else:
84 path = path[:-1]
86 return RootHttpUrl(
87 urlunsplit(
88 (
89 parsed.scheme,
90 parsed.netloc,
91 "/".join(path),
92 parsed.query,
93 parsed.fragment,
94 )
95 )
96 )
98 @property
99 def parents(self) -> Iterable[RootHttpUrl]:
100 """iterate over all URL parents (max 100)"""
101 current = self
102 for _ in range(100):
103 parent = current.parent
104 if current == parent:
105 break
107 current = parent
108 yield parent
111class FtpUrl(_RootUrl[pydantic.FtpUrl]):
112 """An untested FTP URL"""
114 root_model: ClassVar[type[RootModel[Any]]] = RootModel[pydantic.FtpUrl]
115 _validated: pydantic.FtpUrl
117 @property
118 def parent(self) -> FtpUrl:
119 parsed = urlsplit(str(self))
120 path = list(parsed.path.split("/"))[:-1]
121 return FtpUrl(
122 urlunsplit(
123 (
124 parsed.scheme,
125 parsed.netloc,
126 "/".join(path),
127 parsed.query,
128 parsed.fragment,
129 )
130 )
131 )
133 @property
134 def parents(self) -> Iterable[FtpUrl]:
135 """iterate over all URL parents (max 100)"""
136 current = self
137 for _ in range(100):
138 parent = current.parent
139 if current == parent:
140 break
142 current = parent
143 yield parent