-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidators.py
More file actions
182 lines (130 loc) · 4.3 KB
/
validators.py
File metadata and controls
182 lines (130 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""
Input validation utilities for the Library Management System.
Provides validation functions for ISBN, year, and other book-related data.
"""
import re
from datetime import datetime
class ValidationError(Exception):
"""Raised when validation fails."""
pass
def validate_isbn(isbn: str) -> str:
"""
Validate an ISBN-10 or ISBN-13 number.
Args:
isbn: The ISBN string to validate (may contain hyphens or spaces).
Returns:
The cleaned ISBN (digits only, or digits with X for ISBN-10).
Raises:
ValidationError: If the ISBN format is invalid or checksum fails.
"""
if not isbn:
raise ValidationError("ISBN cannot be empty")
# Handle special case for books without ISBN
if isbn.upper() in ("N/A", "NA", "NONE", "UNKNOWN"):
return "N/A"
# Remove hyphens and spaces, convert to uppercase
cleaned = isbn.replace("-", "").replace(" ", "").upper()
if len(cleaned) == 10:
return _validate_isbn10(cleaned)
elif len(cleaned) == 13:
return _validate_isbn13(cleaned)
else:
raise ValidationError(
f"ISBN must be 10 or 13 characters long, got {len(cleaned)}"
)
def _validate_isbn10(isbn: str) -> str:
"""
Validate an ISBN-10 number.
ISBN-10 checksum: sum of (digit * position) mod 11 should equal 0.
The last character can be 'X' representing 10.
"""
if not re.match(r"^\d{9}[\dX]$", isbn):
raise ValidationError(
"ISBN-10 must contain 9 digits followed by a digit or 'X'"
)
total = 0
for i, char in enumerate(isbn):
if char == "X":
value = 10
else:
value = int(char)
total += value * (10 - i)
if total % 11 != 0:
raise ValidationError("Invalid ISBN-10 checksum")
return isbn
def _validate_isbn13(isbn: str) -> str:
"""
Validate an ISBN-13 number.
ISBN-13 checksum: alternating weights of 1 and 3, sum mod 10 should equal 0.
"""
if not re.match(r"^\d{13}$", isbn):
raise ValidationError("ISBN-13 must contain exactly 13 digits")
total = 0
for i, char in enumerate(isbn):
weight = 1 if i % 2 == 0 else 3
total += int(char) * weight
if total % 10 != 0:
raise ValidationError("Invalid ISBN-13 checksum")
return isbn
def validate_year(year: int) -> int:
"""
Validate a publication year.
Args:
year: The publication year to validate.
Returns:
The validated year.
Raises:
ValidationError: If the year is outside reasonable bounds.
"""
current_year = datetime.now().year
# Allow year 0 for unknown publication years (backward compatibility)
if year == 0:
return year
# Reasonable bounds: first printed book (1450) to next year
min_year = 1450
max_year = current_year + 1
if year < min_year:
raise ValidationError(
f"Publication year {year} is before the first printed books ({min_year})"
)
if year > max_year:
raise ValidationError(
f"Publication year {year} is in the future (current year: {current_year})"
)
return year
def validate_title(title: str) -> str:
"""
Validate and clean a book title.
Args:
title: The book title to validate.
Returns:
The cleaned title.
Raises:
ValidationError: If the title is empty or too long.
"""
if not title:
raise ValidationError("Title cannot be empty")
cleaned = title.strip()
if not cleaned:
raise ValidationError("Title cannot be empty or whitespace only")
if len(cleaned) > 500:
raise ValidationError("Title cannot exceed 500 characters")
return cleaned
def validate_author(author: str) -> str:
"""
Validate and clean an author name.
Args:
author: The author name to validate.
Returns:
The cleaned author name.
Raises:
ValidationError: If the author name is empty or too long.
"""
if not author:
raise ValidationError("Author cannot be empty")
cleaned = author.strip()
if not cleaned:
raise ValidationError("Author cannot be empty or whitespace only")
if len(cleaned) > 300:
raise ValidationError("Author name cannot exceed 300 characters")
return cleaned