• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thombashi / pathvalidate / 15659927879

15 Jun 2025 05:30AM UTC coverage: 96.701% (-0.1%) from 96.799%
15659927879

push

github

thombashi
Update examples

Signed-off-by: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>

295 of 319 branches covered (92.48%)

762 of 788 relevant lines covered (96.7%)

16.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.91
/pathvalidate/error.py
1
"""
2
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
3
"""
4

5
import enum
17✔
6
from typing import Final, Optional
17✔
7

8
from ._const import Platform
17✔
9

10

11
def _to_error_code(code: int) -> str:
17✔
12
    return f"PV{code:04d}"
17✔
13

14

15
class ErrorAttrKey:
17✔
16
    BYTE_COUNT: Final = "byte_count"
17✔
17
    DESCRIPTION: Final = "description"
17✔
18
    FS_ENCODING: Final = "fs_encoding"
17✔
19
    PLATFORM: Final = "platform"
17✔
20
    REASON: Final = "reason"
17✔
21
    RESERVED_NAME: Final = "reserved_name"
17✔
22
    REUSABLE_NAME: Final = "reusable_name"
17✔
23
    VALUE: Final = "value"
17✔
24

25

26
@enum.unique
17✔
27
class ErrorReason(enum.Enum):
17✔
28
    """
29
    Validation error reasons.
30
    """
31

32
    NULL_NAME = (_to_error_code(1001), "NULL_NAME", "the value must not be an empty string")
17✔
33
    RESERVED_NAME = (
17✔
34
        _to_error_code(1002),
35
        "RESERVED_NAME",
36
        "found a reserved name by a platform",
37
    )
38
    INVALID_CHARACTER = (
17✔
39
        _to_error_code(1100),
40
        "INVALID_CHARACTER",
41
        "invalid characters found",
42
    )
43
    INVALID_LENGTH = (
17✔
44
        _to_error_code(1101),
45
        "INVALID_LENGTH",
46
        "found an invalid string length",
47
    )
48
    FOUND_ABS_PATH = (
17✔
49
        _to_error_code(1200),
50
        "FOUND_ABS_PATH",
51
        "found an absolute path where must be a relative path",
52
    )
53
    MALFORMED_ABS_PATH = (
17✔
54
        _to_error_code(1201),
55
        "MALFORMED_ABS_PATH",
56
        "found a malformed absolute path",
57
    )
58
    INVALID_AFTER_SANITIZE = (
17✔
59
        _to_error_code(2000),
60
        "INVALID_AFTER_SANITIZE",
61
        "found invalid value after sanitizing",
62
    )
63

64
    @property
17✔
65
    def code(self) -> str:
17✔
66
        """str: Error code."""
67
        return self.__code
17✔
68

69
    @property
17✔
70
    def name(self) -> str:
17✔
71
        """str: Error reason name."""
72
        return self.__name
17✔
73

74
    @property
17✔
75
    def description(self) -> str:
17✔
76
        """str: Error reason description."""
77
        return self.__description
17✔
78

79
    def __init__(self, code: str, name: str, description: str) -> None:
17✔
80
        self.__name = name
17✔
81
        self.__code = code
17✔
82
        self.__description = description
17✔
83

84
    def __str__(self) -> str:
17✔
85
        return f"[{self.__code}] {self.__description}"
17✔
86

87

88
class ValidationError(ValueError):
17✔
89
    """
90
    Exception class of validation errors.
91
    """
92

93
    @property
17✔
94
    def platform(self) -> Optional[Platform]:
17✔
95
        """
96
        :py:class:`~pathvalidate.Platform`: Platform information.
97
        """
98
        return self.__platform
17✔
99

100
    @property
17✔
101
    def reason(self) -> ErrorReason:
17✔
102
        """
103
        :py:class:`~pathvalidate.error.ErrorReason`: The cause of the error.
104
        """
105
        return self.__reason
17✔
106

107
    @property
17✔
108
    def description(self) -> Optional[str]:
17✔
109
        """Optional[str]: Error description."""
110
        return self.__description
17✔
111

112
    @property
17✔
113
    def reserved_name(self) -> str:
17✔
114
        """str: Reserved name."""
115
        return self.__reserved_name
17✔
116

117
    @property
17✔
118
    def reusable_name(self) -> Optional[bool]:
17✔
119
        """Optional[bool]: Whether the name is reusable or not."""
120
        return self.__reusable_name
17✔
121

122
    @property
17✔
123
    def fs_encoding(self) -> Optional[str]:
17✔
124
        """Optional[str]: File system encoding."""
125
        return self.__fs_encoding
17✔
126

127
    @property
17✔
128
    def byte_count(self) -> Optional[int]:
17✔
129
        """Optional[int]: Byte count of the path."""
130
        return self.__byte_count
17✔
131

132
    def __init__(self, *args, **kwargs) -> None:  # type: ignore
17✔
133
        if ErrorAttrKey.REASON not in kwargs:
17!
134
            raise ValueError(f"{ErrorAttrKey.REASON} must be specified")
×
135

136
        self.__reason: ErrorReason = kwargs.pop(ErrorAttrKey.REASON)
17✔
137
        self.__byte_count: Optional[int] = kwargs.pop(ErrorAttrKey.BYTE_COUNT, None)
17✔
138
        self.__platform: Optional[Platform] = kwargs.pop(ErrorAttrKey.PLATFORM, None)
17✔
139
        self.__description: Optional[str] = kwargs.pop(ErrorAttrKey.DESCRIPTION, None)
17✔
140
        self.__reserved_name: str = kwargs.pop(ErrorAttrKey.RESERVED_NAME, "")
17✔
141
        self.__reusable_name: Optional[bool] = kwargs.pop(ErrorAttrKey.REUSABLE_NAME, None)
17✔
142
        self.__fs_encoding: Optional[str] = kwargs.pop(ErrorAttrKey.FS_ENCODING, None)
17✔
143
        self.__value: Optional[str] = kwargs.pop(ErrorAttrKey.VALUE, None)
17✔
144

145
        try:
17✔
146
            super().__init__(*args[0], **kwargs)
17✔
147
        except IndexError:
17✔
148
            super().__init__(*args, **kwargs)
17✔
149

150
    def as_slog(self) -> dict[str, str]:
17✔
151
        """Return a dictionary representation of the error.
152

153
        Returns:
154
            Dict[str, str]: A dictionary representation of the error.
155
        """
156

157
        slog: dict[str, str] = {
17✔
158
            "code": self.reason.code,
159
            ErrorAttrKey.DESCRIPTION: self.reason.description,
160
        }
161
        if self.platform:
17!
162
            slog[ErrorAttrKey.PLATFORM] = self.platform.value
17✔
163
        if self.description:
17!
164
            slog[ErrorAttrKey.DESCRIPTION] = self.description
17✔
165
        if self.__reusable_name is not None:
17!
166
            slog[ErrorAttrKey.REUSABLE_NAME] = str(self.__reusable_name)
×
167
        if self.__fs_encoding:
17!
168
            slog[ErrorAttrKey.FS_ENCODING] = self.__fs_encoding
×
169
        if self.__byte_count:
17!
170
            slog[ErrorAttrKey.BYTE_COUNT] = str(self.__byte_count)
×
171
        if self.__value:
17!
172
            slog[ErrorAttrKey.VALUE] = self.__value
×
173

174
        return slog
17✔
175

176
    def __str__(self) -> str:
17✔
177
        item_list = []
17✔
178
        header = str(self.reason)
17✔
179

180
        if Exception.__str__(self):
17✔
181
            item_list.append(Exception.__str__(self))
17✔
182

183
        if self.platform:
17✔
184
            item_list.append(f"{ErrorAttrKey.PLATFORM}={self.platform.value}")
17✔
185
        if self.description:
17✔
186
            item_list.append(f"{ErrorAttrKey.DESCRIPTION}={self.description}")
17✔
187
        if self.__reusable_name is not None:
17✔
188
            item_list.append(f"{ErrorAttrKey.REUSABLE_NAME}={self.reusable_name}")
17✔
189
        if self.__fs_encoding:
17✔
190
            item_list.append(f"{ErrorAttrKey.FS_ENCODING}={self.__fs_encoding}")
17✔
191
        if self.__byte_count is not None:
17✔
192
            item_list.append(f"{ErrorAttrKey.BYTE_COUNT}={self.__byte_count:,d}")
17✔
193
        if self.__value:
17✔
194
            item_list.append(f"{ErrorAttrKey.VALUE}={self.__value}")
17✔
195

196
        if item_list:
17✔
197
            header += ": "
17✔
198

199
        return header + ", ".join(item_list).strip()
17✔
200

201
    def __repr__(self) -> str:
17✔
202
        return self.__str__()
×
203

204

205
class NullNameError(ValidationError):
17✔
206
    """[Deprecated]
207
    Exception raised when a name is empty.
208
    """
209

210
    def __init__(self, *args, **kwargs) -> None:  # type: ignore
17✔
211
        kwargs[ErrorAttrKey.REASON] = ErrorReason.NULL_NAME
×
212

213
        super().__init__(args, **kwargs)
×
214

215

216
class InvalidCharError(ValidationError):
17✔
217
    """
218
    Exception raised when includes invalid character(s) within a string.
219
    """
220

221
    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
17✔
222
        kwargs[ErrorAttrKey.REASON] = ErrorReason.INVALID_CHARACTER
17✔
223

224
        super().__init__(args, **kwargs)
17✔
225

226

227
class ReservedNameError(ValidationError):
17✔
228
    """
229
    Exception raised when a string matched a reserved name.
230
    """
231

232
    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
17✔
233
        kwargs[ErrorAttrKey.REASON] = ErrorReason.RESERVED_NAME
17✔
234

235
        super().__init__(args, **kwargs)
17✔
236

237

238
class ValidReservedNameError(ReservedNameError):
17✔
239
    """[Deprecated]
240
    Exception raised when a string matched a reserved name.
241
    However, it can be used as a name.
242
    """
243

244
    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
17✔
245
        kwargs[ErrorAttrKey.REUSABLE_NAME] = True
×
246

247
        super().__init__(args, **kwargs)
×
248

249

250
class InvalidReservedNameError(ReservedNameError):
17✔
251
    """[Deprecated]
252
    Exception raised when a string matched a reserved name.
253
    Moreover, the reserved name is invalid as a name.
254
    """
255

256
    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
17✔
257
        kwargs[ErrorAttrKey.REUSABLE_NAME] = False
×
258

259
        super().__init__(args, **kwargs)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc