Skip to content

Commit 2b9d558

Browse files
committed
Refactor of the serialize and to_serializable function
This commit tries to tackle #268 and rewrite the `serialize` function to handle less class specific cases and move it into the single dispatch function `to_serializable`.
1 parent 94e1504 commit 2b9d558

File tree

1 file changed

+75
-31
lines changed

1 file changed

+75
-31
lines changed

pytm/pytm.py

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,7 @@ def get_table(db, klass):
12691269
for e in TM._threats + TM._data + TM._elements + self.findings + [self]:
12701270
table = get_table(db, e.__class__)
12711271
row = {}
1272-
for k, v in serialize(e).items():
1272+
for k, v in to_serializable(e).items():
12731273
if k == "id":
12741274
k = "SID"
12751275
row[k] = ", ".join(str(i) for i in v) if isinstance(v, list) else v
@@ -1278,7 +1278,6 @@ def get_table(db, klass):
12781278
db.close()
12791279

12801280

1281-
12821281
class Controls:
12831282
"""Controls implemented by/on and Element"""
12841283

@@ -2003,54 +2002,99 @@ def to_serializable(val):
20032002

20042003

20052004
@to_serializable.register(TM)
2006-
def ts_tm(obj):
2007-
return serialize(obj, nested=True)
2005+
def _(obj):
2006+
ignore = ("_sf", "_duplicate_ignored_attrs", "_threats", "_elements")
2007+
result = {}
2008+
for attr_name in dir(obj):
2009+
if (
2010+
attr_name.startswith("__")
2011+
or callable(getattr(obj.__class__, attr_name, {}))
2012+
or attr_name in ignore
2013+
):
2014+
# ignore all functions and __atrributes and ignore attributes
2015+
continue
2016+
value = getattr(obj, attr_name)
2017+
if isinstance(value, (Element, Data)):
2018+
value = value.name
2019+
result[attr_name.lstrip("_")] = value
2020+
result["elements"] = [e for e in obj._elements if isinstance(e, (Actor, Asset))]
2021+
return result
20082022

20092023

20102024
@to_serializable.register(Controls)
20112025
@to_serializable.register(Data)
2026+
@to_serializable.register(Finding)
2027+
def _(obj):
2028+
return serialize(obj)
2029+
2030+
20122031
@to_serializable.register(Threat)
2013-
@to_serializable.register(Element)
2032+
def _(obj):
2033+
return serialize(obj, replace={"target": [v.__name__ for v in obj.target]})
2034+
2035+
20142036
@to_serializable.register(Finding)
2037+
def _(obj):
2038+
return serialize(obj, ignore=["element"])
2039+
2040+
2041+
@to_serializable.register(Element)
20152042
def ts_element(obj):
2016-
return serialize(obj, nested=False)
2043+
return serialize(obj,
2044+
ignore=("_is_drawn", "uuid"),
2045+
replace={
2046+
"levels": list(obj.levels),
2047+
"sourceFiles": list(obj.sourceFiles),
2048+
"findings": [v.id for v in obj.findings],
2049+
})
2050+
2051+
2052+
@to_serializable.register(Actor)
2053+
@to_serializable.register(Asset)
2054+
def _(obj):
2055+
# Note that we use the ts_element function defined for the Element class
2056+
result = ts_element(obj)
2057+
result["__class__"] = obj.__class__.__name__
2058+
return result
2059+
2060+
2061+
def serialize(obj, ignore=None, replace=None):
2062+
"""
2063+
Serialize an object into a dictionary.
20172064
2065+
Keyword arguments:
2066+
ignore -- a collection of attribute names, which are not included in the serialized dictionary
2067+
replace -- dictionary keyed with attribute names which should be replaced by the diven value.
2068+
"""
2069+
if ignore is None:
2070+
ignore = []
2071+
if replace is None:
2072+
replace = {}
20182073

2019-
def serialize(obj, nested=False):
2020-
"""Used if *obj* is an instance of TM, Element, Threat or Finding."""
2021-
klass = obj.__class__
20222074
result = {}
2023-
if isinstance(obj, (Actor, Asset)):
2024-
result["__class__"] = klass.__name__
2025-
for i in dir(obj):
2075+
for attr_name in dir(obj):
20262076
if (
2027-
i.startswith("__")
2028-
or callable(getattr(klass, i, {}))
2029-
or (
2030-
isinstance(obj, TM)
2031-
and i in ("_sf", "_duplicate_ignored_attrs", "_threats")
2032-
)
2033-
or (isinstance(obj, Element) and i in ("_is_drawn", "uuid"))
2034-
or (isinstance(obj, Finding) and i == "element")
2077+
attr_name.startswith("__")
2078+
or callable(getattr(obj.__class__, attr_name, {}))
2079+
or attr_name in ignore
20352080
):
2081+
# ignore all functions and __atrributes and ignore attributes
20362082
continue
2037-
value = getattr(obj, i)
2038-
if isinstance(obj, TM) and i == "_elements":
2039-
value = [e for e in value if isinstance(e, (Actor, Asset))]
2083+
try:
2084+
result[attr_name] = replace[attr_name]
2085+
continue
2086+
except KeyError:
2087+
pass
2088+
value = getattr(obj, attr_name)
20402089
if value is not None:
20412090
if isinstance(value, (Element, Data)):
20422091
value = value.name
2043-
elif isinstance(obj, Threat) and i == "target":
2044-
value = [v.__name__ for v in value]
2045-
elif i in ("levels", "sourceFiles", "assumptions"):
2046-
value = list(value)
20472092
elif (
2048-
not nested
2049-
and not isinstance(value, str)
2093+
not isinstance(value, str)
20502094
and isinstance(value, Iterable)
20512095
):
2052-
value = [v.id if isinstance(v, Finding) else v.name for v in value]
2053-
result[i.lstrip("_")] = value
2096+
value = [v.name for v in value]
2097+
result[attr_name.lstrip("_")] = value
20542098
return result
20552099

20562100

0 commit comments

Comments
 (0)