Skip to content

Commit e0f2e4e

Browse files
Replace None comparisons with 'is/is not' operators. Updated API with docstrings. (#271)
Signed-off-by: Andriy Kokhan <[email protected]>
1 parent e311383 commit e0f2e4e

File tree

11 files changed

+126
-30
lines changed

11 files changed

+126
-30
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ SAI Challenger has many applications. A partial list is below:
2222
* CI/CD and regression testing of virtual or physical DUTs
2323
* DUT performance testing using HW traffic generators
2424
* PHY (transceiver) device testing & qualification
25-
* Ubrella test harness for native SAI Challenger test cases as well as legacy SAI-PTF test cases, using a single-pane-of-glass to reduce testbed complexity.
25+
* Umbrella test harness for native SAI Challenger test cases as well as legacy SAI-PTF test cases, using a single-pane-of-glass to reduce testbed complexity.
2626

2727
# Use-case scenarios
2828
SAI Challenger has many configuration options, resulting in numerous permutations of:

common/sai.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,46 @@
99

1010
class CommandProcessor:
1111
"""
12-
Allow setup scaled configurations with referenced objects.
13-
Contain reference object cache.
14-
When objects are referenced they are regenerating missing keys/oids from object cache
12+
Process SAI commands with object reference substitution.
13+
14+
This class enables setup of scaled configurations by maintaining a registry
15+
of created objects and substituting object references (variables starting with '$')
16+
with actual OIDs or keys during command processing.
17+
18+
Attributes:
19+
objects_registry: Dictionary mapping object names to their metadata
20+
sai: Reference to parent Sai instance
1521
"""
1622

1723
class SubstitutionError(RuntimeError):
18-
...
24+
"""Raised when object reference substitution fails."""
25+
pass
1926

2027
def __init__(self, sai: 'Sai'):
28+
"""
29+
Initialize CommandProcessor.
30+
31+
Args:
32+
sai: Parent Sai instance for API calls
33+
"""
2134
self.objects_registry = {}
2235
self.sai = sai
2336

2437
def _substitute_from_object_registry(self, obj, *args, **kwargs):
38+
"""
39+
Substitute a reference variable with its actual value from registry.
40+
41+
Args:
42+
obj: Object reference (string starting with '$') or regular value
43+
*args: Optional default value as positional argument
44+
**kwargs: Optional default value as 'default' keyword argument
45+
46+
Returns:
47+
Registry entry for the referenced object
48+
49+
Raises:
50+
SubstitutionError: If object is not found in registry and no default provided
51+
"""
2552
if isinstance(obj, str) and obj.startswith('$'):
2653
store_name = obj[1:]
2754
try:
@@ -35,6 +62,19 @@ def _substitute_from_object_registry(self, obj, *args, **kwargs):
3562
raise self.SubstitutionError
3663

3764
def substitute_command_from_object_registry(self, command):
65+
"""
66+
Substitute all object references in a command with actual values.
67+
68+
Processes command dictionary and replaces:
69+
- '$VARIABLE' references in keys with actual OIDs/keys
70+
- '$VARIABLE' references in attributes with actual values
71+
72+
Args:
73+
command: Dictionary containing command with possible references
74+
75+
Returns:
76+
New command dictionary with all references substituted
77+
"""
3878
substituted_command = {}
3979
store_name = command.get("name")
4080

@@ -86,7 +126,9 @@ def substitute_command_from_object_registry(self, command):
86126
return substituted_command
87127

88128
def process_command(self, command):
89-
'''
129+
"""
130+
Process a single SAI command (create, set, get, or remove).
131+
90132
Command examples:
91133
{
92134
"name": "vip1"
@@ -105,7 +147,16 @@ def process_command(self, command):
105147
"type" : "SAI_OBJECT_TYPE_DASH_ACL_GROUP",
106148
"attributes" : [ "SAI_DASH_ACL_GROUP_ATTR_IP_ADDR_FAMILY", "SAI_IP_ADDR_FAMILY_IPV4" ]
107149
},
108-
'''
150+
151+
Args:
152+
command: Dictionary with 'name', 'op', 'type', and optional 'key'/'attributes'
153+
154+
Returns:
155+
Created OID for 'create', attribute values for 'get', or operation status
156+
157+
Raises:
158+
AssertionError: If command format is invalid or operation fails
159+
"""
109160
store_name = command.get("name")
110161
assert store_name, f"Invalid command {command}. Entry name is undefined"
111162

common/sai_client/sai_thrift_client/sai_thrift_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@ def get(self, obj, attrs, do_assert=True):
126126
return status, SaiData(result)
127127

128128
def get_object_type(self, oid, default=None) -> SaiObjType:
129-
if default != None:
129+
if default is not None:
130130
return ThriftConverter.convert_to_sai_obj_type(default)
131131

132-
if oid != None:
132+
if oid is not None:
133133
try:
134134
obj_type = self.sai_type_map.get(oid, None)
135135
if obj_type is None:

common/sai_client/sai_thrift_client/sai_thrift_utils.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def convert_value_to_thrift(value, attr_name=None, value_type=None):
120120
'macsecsak', 'macsecauthkey', 'macsecsalt' ]:
121121
if isinstance(value, str):
122122
actual_value = getattr(sai_headers, value, None)
123-
if actual_value != None:
123+
if actual_value is not None:
124124
return actual_value
125125
if value == '':
126126
return 0
@@ -393,7 +393,7 @@ def object_id(oid):
393393
"16" => 16
394394
"oid:0x10" => 16
395395
"""
396-
if oid == None or oid == 'null' or oid == 'SAI_NULL_OBJECT_ID':
396+
if oid is None or oid == 'null' or oid == 'SAI_NULL_OBJECT_ID':
397397
return 0
398398
if isinstance(oid, str) and oid.startswith('oid:0x'):
399399
return int(oid[4:], 16)
@@ -412,7 +412,7 @@ def get_value_type_by_thrift_spec(thrift_spec):
412412
# FIXME: Sometimes, thrift_spec returns "None" for both "oid" and "int"
413413
# E.g., For SAI_OBJECT_TYPE_DIRECTION_LOOKUP_ENTRY, thrift_spec will be
414414
# (1, 10, 'switch_id', None, None), (2, 8, 'vni', None, None)
415-
if thrift_spec == None:
415+
if thrift_spec is None:
416416
return "oid"
417417

418418
attribute_value_spec = getattr(ttypes, f'sai_thrift_attribute_value_t').thrift_spec
@@ -441,7 +441,7 @@ def convert_value_from_thrift(value, attr_name, obj_type=None):
441441
return str(value) if value > 0 else str(value & 0xFFFF)
442442
elif value_type in [ 's32' ]:
443443
actual_value = ThriftConverter.get_str_by_enum(obj_type, attr_name, value)
444-
if actual_value != None:
444+
if actual_value is not None:
445445
return actual_value
446446
return str(value)
447447
elif value_type in [ 'booldata' ]:
@@ -497,7 +497,7 @@ def from_sai_int_list(value_type, object_list, attr_name=None, obj_type=None):
497497
for ii in range(object_list.count):
498498
if value_type == 's32list' and attr_name is not None:
499499
actual_value = ThriftConverter.get_str_by_enum(obj_type, attr_name, listvar[ii])
500-
if actual_value != None:
500+
if actual_value is not None:
501501
listvar[ii] = actual_value
502502
result += str(listvar[ii])
503503
result += ","
@@ -644,7 +644,7 @@ def get_str_by_enum(obj_type, attr_name, enum_value):
644644
meta = ThriftConverter.get_sai_meta(obj_type, attr_name)
645645
if meta is None:
646646
return None
647-
if meta['properties'].get('values') == None:
647+
if meta['properties'].get('values') is None:
648648
return str(enum_value)
649649
for k, v in meta['properties']['values'].items():
650650
if v == enum_value:

common/sai_data.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,22 @@
33

44

55
class SaiObjType(Enum):
6+
"""
7+
Enumeration for SAI object types.
8+
9+
This enum can be dynamically populated from either Thrift headers
10+
or SAI JSON metadata files.
11+
"""
612

713
@staticmethod
814
def generate_from_thrift():
15+
"""
16+
Generate SAI object type enum from Thrift headers.
17+
18+
Dynamically populates the enum with SAI_OBJECT_TYPE_* constants
19+
found in the sai_thrift.sai_headers module. Skips if enum is
20+
already populated.
21+
"""
922
# Skip generation in case enum is not empty
1023
if list(SaiObjType):
1124
return
@@ -35,6 +48,12 @@ def generate_from_thrift():
3548

3649
@staticmethod
3750
def generate_from_json():
51+
"""
52+
Generate SAI object type enum from JSON metadata.
53+
54+
Dynamically populates the enum with SAI object types from
55+
/etc/sai/sai.json metadata file. Skips if enum is already populated.
56+
"""
3857
# Skip generation in case enum is not empty
3958
if list(SaiObjType):
4059
return
@@ -50,6 +69,12 @@ def generate_from_json():
5069

5170

5271
class SaiStatus(Enum):
72+
"""
73+
Enumeration for SAI operation status codes.
74+
75+
Contains all standard SAI status codes including success, failures,
76+
and attribute-specific error codes.
77+
"""
5378
SUCCESS = 0x00000000
5479
FAILURE = -0x00000001
5580
NOT_SUPPORTED = -0x00000002
@@ -87,22 +112,39 @@ class SaiStatus(Enum):
87112

88113

89114
class SaiData:
115+
"""
116+
Wrapper class for SAI data returned from API calls.
117+
118+
Provides convenient methods to parse and extract data in various
119+
formats (JSON, OID, lists, counters, etc.).
120+
"""
121+
90122
def __init__(self, data):
123+
"""
124+
Initialize SaiData with raw data.
125+
126+
Args:
127+
data: Raw data string from SAI API call
128+
"""
91129
self.data = data
92130

93131
def raw(self):
132+
"""Return raw data string."""
94133
return self.data
95134

96135
def to_json(self):
136+
"""Parse and return data as JSON."""
97137
return json.loads(self.data)
98138

99139
def oid(self, idx=1):
140+
"""Extract OID from JSON data at the specified index."""
100141
value = self.to_json()[idx]
101142
if "oid:" in value:
102143
return value
103144
return "oid:0x0"
104145

105146
def to_list(self, idx=1):
147+
"""Convert data at index to a list by parsing format 'count:item1,item2,...'."""
106148
value = self.to_json()[idx]
107149
n_items, _, items = value.partition(':')
108150
if n_items.isdigit():
@@ -111,13 +153,15 @@ def to_list(self, idx=1):
111153
return []
112154

113155
def oids(self, idx=1):
156+
"""Extract list of OIDs from data at the specified index."""
114157
value = self.to_list(idx)
115158
if len(value) > 0:
116159
if "oid:" in value[0]:
117160
return value
118161
return []
119162

120163
def counters(self):
164+
"""Parse statistics counters into a dictionary."""
121165
i = 0
122166
cntrs_dict = {}
123167
value = self.to_json()
@@ -127,12 +171,14 @@ def counters(self):
127171
return cntrs_dict
128172

129173
def value(self):
174+
"""Extract the value from JSON data, normalizing boolean values."""
130175
if self.to_json()[1].lower() in ["true", "false"]:
131176
# Thrift to Redis compatibility
132177
return self.to_json()[1].lower()
133178
return self.to_json()[1]
134179

135180
def uint32(self):
181+
"""Extract and return value as unsigned 32-bit integer."""
136182
v = self.value()
137183
assert v.isdigit(), f"Unexpected {self.to_json()[0]} value {v} "
138184
return int(v)

common/sai_dataplane/ptf/sai_ptf_dataplane.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def __init__(self, cfg=None):
9494
def setUp(self):
9595
assert self.dataplane is not None
9696
self.dataplane.flush()
97-
if config["log_dir"] != None:
97+
if config["log_dir"] is not None:
9898
filename = os.path.join(config["log_dir"], str(self)) + ".pcap"
9999
self.dataplane.start_pcap(filename)
100100

@@ -106,7 +106,7 @@ def at_receive(self, pkt, device_number=0, port_number=-1):
106106

107107
def tearDown(self):
108108
assert self.dataplane is not None
109-
if config["log_dir"] != None:
109+
if config["log_dir"] is not None:
110110
self.dataplane.stop_pcap()
111111

112112
@staticmethod
@@ -135,7 +135,7 @@ def __logging_setup(config):
135135

136136
logging.getLogger().setLevel(DEBUG_LEVELS[config["debug"]])
137137

138-
if config["log_dir"] != None:
138+
if config["log_dir"] is not None:
139139
if os.path.exists(config["log_dir"]):
140140
import shutil
141141
shutil.rmtree(config["log_dir"])
@@ -234,7 +234,7 @@ def init(self):
234234

235235
# Set up the dataplane
236236
dataplane_instance = ptf.dataplane.DataPlane(config)
237-
if config["log_dir"] == None:
237+
if config["log_dir"] is None:
238238
filename = os.path.splitext(config["log_file"])[0] + '.pcap'
239239
dataplane_instance.start_pcap(filename)
240240

common/sai_dataplane/sai_hostif_dataplane.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def init(self):
1717
_config = copy.deepcopy(config)
1818
_config["platform"] = "nn"
1919
self.dataplane = DataPlane(_config)
20-
if _config["log_dir"] == None:
20+
if _config["log_dir"] is None:
2121
filename = os.path.splitext(_config["log_file"])[0] + '.pcap'
2222
self.dataplane.start_pcap(filename)
2323

common/sai_dataplane/snappi/sai_snappi_dataplane.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def add_ethernet_header(self,
253253
src_count=1,
254254
src_step="00:00:00:00:00:01"
255255
):
256-
if flow == None:
256+
if flow is None:
257257
return None
258258

259259
ether = flow.packet.add().ethernet
@@ -285,7 +285,7 @@ def add_ipv4_header(self,
285285
src_count=1,
286286
src_step="0.0.0.1"
287287
):
288-
if flow == None:
288+
if flow is None:
289289
return None
290290

291291
ipv4 = flow.packet.add().ipv4
@@ -309,7 +309,7 @@ def add_udp_header(self,
309309
src_count=1,
310310
src_step=1
311311
):
312-
if flow == None:
312+
if flow is None:
313313
return None
314314

315315
udp = flow.packet.add().udp
@@ -329,7 +329,7 @@ def add_vxlan_header(self,
329329
vni_count=1,
330330
vni_step=1
331331
):
332-
if flow == None:
332+
if flow is None:
333333
return None
334334

335335
vxlan = flow.packet.add().vxlan

0 commit comments

Comments
 (0)