2222# =============================================================================
2323
2424
25- def isuntested (value ):
26- # pylint: disable=unused-argument
27- """dummy function for untested values"""
25+ def is_untested (_val ) -> bool :
26+ """Dummy validator for untested fields, always passes."""
2827 return True
2928
3029
31- def isvalidhostname (hostname ):
32- """
33- test for valid short hostname with letters, numbers, and dashes
34- cannot begin or end with a dash
35- """
36- pattern = r"^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$"
37- result = re .match (pattern , hostname )
38- if result :
39- return True
40- return False
30+ def is_valid_hostname (val : str ) -> bool :
31+ """Test for valid short hostname: letters, numbers, dashes. No leading/trailing dash."""
32+ return bool (re .match (r"^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$" , val ))
4133
4234
43- def is_valid_asset_id (asset_id ):
44- """
45- test for valid asset ID, which has the same constraints as a hostname
46- """
47- return isvalidhostname (asset_id )
35+ def is_valid_asset_id (val : str ) -> bool :
36+ """Test for valid asset ID (same constraints as hostname)."""
37+ return is_valid_hostname (val )
4838
4939
50- def isvalidmodel (model ):
51- """
52- test for valid switch model (enumerated)
53- """
54- return model in {
40+ def is_valid_switch_model (val : str ) -> bool :
41+ """Test for valid switch model (enumerated)."""
42+ return val in {
5543 "ex2200-48p" ,
5644 "ex2200-48t" ,
5745 "ex2200-24p" ,
@@ -70,62 +58,62 @@ def isvalidmodel(model):
7058 }
7159
7260
73- def isvalidip ( addr ) :
74- """test for valid v4 or v6 ip """
61+ def is_valid_ipv4_address ( val : str ) -> bool :
62+ """Test for valid IPv4 address. """
7563 try :
76- ipaddress .ip_address (addr )
64+ ipaddress .IPv4Address (val )
65+ return True
7766 except ValueError :
7867 return False
79- return True
8068
8169
82- def is_valid_v6_suffix (suffix ):
83- """
84- test for valid v6 suffix
85- """
86- pattern = r"^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4})*$"
87-
88- if not re .match (pattern , suffix ):
70+ def is_valid_ipv6_address (val : str ) -> bool :
71+ """Test for valid IPv6 address."""
72+ try :
73+ ipaddress .IPv6Address (val )
74+ return True
75+ except ValueError :
8976 return False
9077
91- groups = suffix .split (":" )
92- return all (group and len (group ) <= 4 for group in groups )
78+
79+ def is_valid_ipv6_suffix (val : str ) -> bool :
80+ """Test for valid IPv6 suffix by prepending a dummy prefix."""
81+ return is_valid_ipv6_address (f"fe80::{ val } " )
9382
9483
95- def isvalidsubnet ( subnet ) :
96- """test for valid v4 or v6 subnet"""
84+ def is_valid_ipv4_subnet ( val : str ) -> bool :
85+ """Test for valid IPv4 subnet. """
9786 try :
98- ipaddress .ip_network (subnet , strict = True )
87+ ipaddress .IPv4Network (val , strict = True )
88+ return True
9989 except ValueError :
10090 return False
101- return True
10291
10392
104- def isvalidiporempty (val ):
105- """test for valid ip or empty"""
106- return isvalidip (val ) or val == ""
93+ def is_valid_ipv6_subnet (val : str ) -> bool :
94+ """Test for valid IPv6 subnet."""
95+ try :
96+ ipaddress .IPv6Network (val , strict = True )
97+ return True
98+ except ValueError :
99+ return False
107100
108101
109- def isvalidmac (macaddr ):
110- """test for valid colon seperate mac address"""
111- pattern = r"^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})$"
112- result = re .match (pattern , macaddr )
113- if result :
114- return True
115- return False
102+ # try to avoid regex but netaddr.mac_unix_expanded would lead to
103+ # surprises, this is readable enough
104+ def is_valid_mac_address (val : str ) -> bool :
105+ """Test for valid colon-separated MAC address."""
106+ return bool (re .match (r"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$" , val ))
116107
117108
118- def isvalidwifi24chan ( chan ) :
119- """test for valid 2.4Ghz WiFi channel"""
120- return isint ( chan ) and int (chan ) in {1 , 6 , 11 }
109+ def is_valid_wifi_24ghz_chan ( val : int | str ) -> bool :
110+ """Test for valid 2.4GHz WiFi channel. """
111+ return is_non_negative_int ( val ) and int (val ) in {1 , 6 , 11 }
121112
122113
123- def isvalidwifi5chan (chan ):
124- """
125- test for valid 5Ghz WiFi channel
126- allows DFS channels
127- """
128- return isint (chan ) and int (chan ) in {
114+ def is_valid_wifi_5ghz_chan (val : int | str ) -> bool :
115+ """Test for valid 5GHz WiFi channel, including DFS channels."""
116+ return is_non_negative_int (val ) and int (val ) in {
129117 32 ,
130118 36 ,
131119 40 ,
@@ -158,71 +146,34 @@ def isvalidwifi5chan(chan):
158146 }
159147
160148
161- def is_valid_pi_vlan (vlan ):
162- """test for valid PI vlan"""
163- # we currently constrain PI use to 3 existing vlans
164- # this can be extended later as needed
165- return isint (vlan ) and int (vlan ) in {
166- 107 ,
167- 110 ,
168- 507 ,
169- }
149+ def is_valid_pi_vlan (val : int | str ) -> bool :
150+ """Test for valid PI VLAN: currently constrained to 107, 110, 507."""
151+ return is_non_negative_int (val ) and int (val ) in {107 , 110 , 507 }
170152
171153
172- def isint (val ):
173- """test for integer"""
154+ def is_non_negative_int (val : int | str ) -> bool :
155+ """Test for non-negative integer (0 or greater)."""
156+ if isinstance (val , int ):
157+ return val >= 0
174158 return val .isdigit ()
175159
176160
177- def isintorempty (val ):
178- """test for integer or empty"""
179- return val .isdigit () or val == ""
180-
181-
182- def isvalidhierarchy (val ):
183- """test for valid switch hierarchy"""
184- pattern = r"^([A-Z]+[.][0-9])$"
185- result = re .match (pattern , val )
186- if result :
187- return True
188- return False
161+ def is_valid_switch_hierarchy (val : str ) -> bool :
162+ """Test for valid switch hierarchy (e.g. ABC.1)."""
163+ return bool (re .match (r"^[A-Z]+\.[0-9]$" , val ))
189164
190165
191- def isvalid_p_o_e (val ):
192- """test for valid POE flag"""
193- pattern = r"^(POE)|(-)$"
194- result = re .match (pattern , val )
195- if result :
196- return True
197- return False
166+ def is_in_ap_list (val : str ) -> bool :
167+ """Test for existence of serial number in aps.csv."""
168+ df = pd .read_csv ("aps/aps.csv" )
169+ return val in df ["serial" ].values
198170
199171
200- def isvalidnoiselevel (val ):
201- """test for valid noise level [Quiet, Normal, Loud]"""
202- if val in ["Quiet" , "Normal" , "Loud" , "??" ]:
203- return True
204- return False
205-
206-
207- def isinaplist (val ):
208- """test for existence of the value in apuse.csv"""
209- lines = []
210- with open ("aps/aps.csv" , "r" , encoding = "utf-8" ) as fh :
211- lines = fh .readlines ()
212- aplist = set ()
213- for line in lines [1 :]:
214- cols = line .split ("," )
215- aplist .add (cols [0 ])
216- return val in aplist
217-
218-
219- def isvalidtype (val ):
172+ def is_valid_switch_type (val : str ) -> bool :
220173 """test for valid switch type, denoted by existence of file in types dir"""
221174 type_path = "../switch-configuration/config/types/"
222175 valid = [f for f in listdir (type_path ) if isfile (join (type_path , f ))]
223- if val in valid :
224- return True
225- return False
176+ return val in valid
226177
227178
228179def is_valid_map_coordinate (val : int | float | str ) -> bool :
@@ -408,7 +359,7 @@ def validate_dataframe(
408359 Args:
409360 df: DataFrame to validate
410361 validators: List of validator functions, one per column.
411- Use None or isuntested to skip validation for a column.
362+ Use None or is_untested to skip validation for a column.
412363 filename: Filename for error messages
413364 skip_header: If True, skip the first row
414365
@@ -422,7 +373,7 @@ def validate_dataframe(
422373 # Skip if no validator or beyond available columns
423374 if validator is None or col_idx >= len (df .columns ):
424375 continue
425- if validator is isuntested :
376+ if validator is is_untested :
426377 continue
427378
428379 value = df .iloc [row_idx , col_idx ]
0 commit comments