Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,16 @@ docs: docs/pytm/index.html docs/threats.md

.PHONY: fmt
fmt:
black $(wildcard pytm/*.py) $(wildcard tests/*.py) $(wildcard *.py)
poetry run ruff format

.PHONY: ana
ana:
poetry run ruff analyze graph

.PHONY: fix
fix:
poetry run ruff check . --fix

.PHONY: check
check:
poetry run ruff check
219 changes: 24 additions & 195 deletions poetry.lock

Large diffs are not rendered by default.

51 changes: 50 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,58 @@ python = ">=3.9,<=3.11.13"
pydal = "~20200714.1"

[tool.poetry.group.dev.dependencies]
black = "^25.9.0"
pdoc3 = "^0.11.6"
ruff = "^0.14.0"

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

[tool.ruff]
target-version = "py310"

# https://docs.astral.sh/ruff/settings/#analyze
[tool.ruff.analyze]
detect-string-imports = false
direction = "dependencies"
exclude = []
include-dependencies = {}
preview = false
string-imports-min-dots = 2

# https://docs.astral.sh/ruff/settings/#format
[tool.ruff.format]
docstring-code-format = true
docstring-code-line-length = "dynamic"
exclude = []
indent-style = "space"
line-ending = "lf"
preview = false
quote-style = "double"
skip-magic-trailing-comma = false

# https://docs.astral.sh/ruff/settings/#lint
[tool.ruff.lint]
allowed-confusables = []
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
exclude = []
explicit-preview-rules = false
extend-fixable = []
extend-ignore = []
extend-per-file-ignores = {}
extend-safe-fixes = []
extend-select = []
extend-unsafe-fixes = []
external = []
fixable = ["ALL"]
future-annotations = false
ignore = []
ignore-init-module-imports = true
logger-objects = []
per-file-ignores = {}
preview = false
select = ["E4", "E7", "E9", "F"]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ruff rule set can be extended here. E.g. to add pylint rules refer to here.

task-tags = ["TODO", "FIXME", "XXX"]
typing-extensions = true
typing-modules = []
unfixable = []
18 changes: 9 additions & 9 deletions pytm/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


def req_reply(src: Element, dest: Element, req_name: str, reply_name=None) -> (DF, DF):
'''
"""
This function creates two datflows where one dataflow is a request
and the second dataflow is the corresponding reply to the newly created request.

Expand All @@ -22,17 +22,17 @@ def req_reply(src: Element, dest: Element, req_name: str, reply_name=None) -> (D
Returns:
a tuple of two dataflows, where the first is the request and the second is the reply.

'''
"""
if not reply_name:
reply_name = f'Reply to {req_name}'
reply_name = f"Reply to {req_name}"
req = DF(src, dest, req_name)
reply = DF(dest, src, name=reply_name)
reply.responseTo = req
return req, reply


def reply(req: DF, **kwargs) -> DF:
'''
"""
This function takes a dataflow as an argument and returns a new dataflow, which is a response to the given dataflow.

Args:
Expand All @@ -45,12 +45,12 @@ def reply(req: DF, **kwargs) -> DF:
client_reply = reply(client_query)
Returns:
a Dataflow which is a reply to the given datadlow req
'''
if 'name' not in kwargs:
name = f'Reply to {req.name}'
"""
if "name" not in kwargs:
name = f"Reply to {req.name}"
else:
name = kwargs['name']
del kwargs['name']
name = kwargs["name"]
del kwargs["name"]
reply = DF(req.sink, req.source, name, **kwargs)
reply.responseTo = req
return req, reply
6 changes: 0 additions & 6 deletions pytm/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@
from .pytm import (
TM,
Boundary,
Element,
Dataflow,
Server,
ExternalEntity,
Datastore,
Actor,
Process,
SetOfProcesses,
Action,
Lambda,
Controls,
)


Expand Down
46 changes: 29 additions & 17 deletions pytm/pytm.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ class varElement(var):
def __set__(self, instance, value):
if not isinstance(value, Element):
raise ValueError(
"expecting an Element (or inherited) "
"value, got a {}".format(type(value))
"expecting an Element (or inherited) value, got a {}".format(
type(value)
)
)
super().__set__(instance, value)

Expand Down Expand Up @@ -262,9 +263,7 @@ def __str__(self):
class varControls(var):
def __set__(self, instance, value):
if not isinstance(value, Controls):
raise ValueError(
f"expecting an Controls value, got a {type(value)}"
)
raise ValueError(f"expecting an Controls value, got a {type(value)}")
super().__set__(instance, value)


Expand All @@ -283,9 +282,7 @@ def __set__(self, instance, value):
class varAssumption(var):
def __set__(self, instance, value):
if not isinstance(value, Assumption):
raise ValueError(
f"expecting an Assumption value, got a {type(value)}"
)
raise ValueError(f"expecting an Assumption value, got a {type(value)}")
super().__set__(instance, value)


Expand Down Expand Up @@ -560,7 +557,7 @@ def _describe_classes(classes):
docs.append("required")
if attr.default or isinstance(attr.default, bool):
docs.append("default: {}".format(attr.default))
lpadding = f'\n{" ":<{longest+2}}'
lpadding = f"\n{' ':<{longest + 2}}"
print(f" {i:<{longest}}{lpadding.join(docs)}")
print()

Expand Down Expand Up @@ -687,7 +684,11 @@ class Finding:
threat_id = varString("", required=True, doc="Threat ID")
references = varString("", required=True, doc="Threat references")
condition = varString("", required=True, doc="Threat condition")
assumption = varAssumption(None, required=False, doc="The assumption, that caused this finding to be excluded")
assumption = varAssumption(
None,
required=False,
doc="The assumption, that caused this finding to be excluded",
)
response = varString(
"",
required=False,
Expand Down Expand Up @@ -799,7 +800,7 @@ class TM:
excluded_findings = varFindings(
[],
doc="Threats found for elements of this model, "
"that were excluded on a per-element basis, using the Assumptions class"
"that were excluded on a per-element basis, using the Assumptions class",
)
onDuplicates = varAction(
Action.NO_ACTION,
Expand Down Expand Up @@ -845,7 +846,9 @@ def _add_threats(self):
raise UIError(
e, f"while trying to open the the threat file ({self.threatsFile})."
)
active_threats = (threat for threat in threats_json if "DEPRECATED" not in threat)
active_threats = (
threat for threat in threats_json if "DEPRECATED" not in threat
)
for threat in active_threats:
TM._threats.append(Threat(**threat))

Expand Down Expand Up @@ -883,7 +886,12 @@ def resolve(self):
for assumption in e.assumptions + global_assumptions: # type: Assumption
if t.id in assumption.exclude:
excluded_finding_count += 1
f = Finding(e, id=str(excluded_finding_count), threat=t, assumption=assumption)
f = Finding(
e,
id=str(excluded_finding_count),
threat=t,
assumption=assumption,
)
excluded_findings.append(f)
_continue = True
break
Expand Down Expand Up @@ -1223,9 +1231,10 @@ def sqlDump(self, filename):
from pydal import DAL, Field
except ImportError as e:
raise UIError(
e, """This feature requires the pyDAL package,
e,
"""This feature requires the pyDAL package,
Please install the package via pip or your packagemanger of choice.
"""
""",
)

@lru_cache(maxsize=None)
Expand Down Expand Up @@ -1278,7 +1287,6 @@ def get_table(db, klass):
db.close()



class Controls:
"""Controls implemented by/on and Element"""

Expand Down Expand Up @@ -1383,8 +1391,12 @@ class Assumption:
Assumption used by an Element.
Used to exclude threats on a per-element basis.
"""

name = varString("", required=True)
exclude = varStrings([], doc="A list of threat SIDs to exclude for this assumption. For example: INP01")
exclude = varStrings(
[],
doc="A list of threat SIDs to exclude for this assumption. For example: INP01",
)
description = varString("", doc="An additional description of the assumption")

def __init__(self, name, **kwargs):
Expand Down
40 changes: 27 additions & 13 deletions pytm/report_util.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,53 @@

class ReportUtils:
@staticmethod
def getParentName(element):
from pytm import Boundary
if (isinstance(element, Boundary)):

if isinstance(element, Boundary):
parent = element.inBoundary
if (parent is not None):
if parent is not None:
return parent.name
else:
return str("")
else:
return "ERROR: getParentName method is not valid for " + element.__class__.__name__

return (
"ERROR: getParentName method is not valid for "
+ element.__class__.__name__
)

@staticmethod
def getNamesOfParents(element):
from pytm import Boundary
if (isinstance(element, Boundary)):
parents = [p.name for p in element.parents()]
return parents

if isinstance(element, Boundary):
parents = [p.name for p in element.parents()]
return parents
else:
return "ERROR: getNamesOfParents method is not valid for " + element.__class__.__name__
return (
"ERROR: getNamesOfParents method is not valid for "
+ element.__class__.__name__
)

@staticmethod
def getFindingCount(element):
from pytm import Element
if (isinstance(element, Element)):

if isinstance(element, Element):
return str(len(list(element.findings)))
else:
return "ERROR: getFindingCount method is not valid for " + element.__class__.__name__
return (
"ERROR: getFindingCount method is not valid for "
+ element.__class__.__name__
)

@staticmethod
def getElementType(element):
from pytm import Element
if (isinstance(element, Element)):

if isinstance(element, Element):
return str(element.__class__.__name__)
else:
return "ERROR: getElementType method is not valid for " + element.__class__.__name__
return (
"ERROR: getElementType method is not valid for "
+ element.__class__.__name__
)
Loading