diff --git a/rules/S7156/python/metadata.json b/rules/S7156/python/metadata.json index e8545bbc3db..d65f13c6334 100644 --- a/rules/S7156/python/metadata.json +++ b/rules/S7156/python/metadata.json @@ -1,13 +1,12 @@ { - "title": "FIXME", + "title": "\"copy.replace\" should not be invoked with an unsupported argument", "type": "CODE_SMELL", "status": "ready", "remediation": { - "func": "Constant\/Issue", + "func": "Constant/Issue", "constantCost": "5min" }, - "tags": [ - ], + "tags": [], "defaultSeverity": "Major", "ruleSpecification": "RSPEC-7156", "sqKey": "S7156", @@ -17,8 +16,7 @@ "code": { "impacts": { "MAINTAINABILITY": "HIGH", - "RELIABILITY": "MEDIUM", - "SECURITY": "LOW" + "RELIABILITY": "MEDIUM" }, "attribute": "CONVENTIONAL" } diff --git a/rules/S7156/python/rule.adoc b/rules/S7156/python/rule.adoc index caae0d69054..ae401215eaa 100644 --- a/rules/S7156/python/rule.adoc +++ b/rules/S7156/python/rule.adoc @@ -1,16 +1,34 @@ FIXME: add a description -// If you want to factorize the description uncomment the following line and create the file. -//include::../description.adoc[] +:object_replacement_protocol: https://docs.python.org/3/library/copy.html#object.__replace__ == Why is this an issue? -FIXME: remove the unused optional headers (that are commented out) +Calling ``++copy.replace(...)++`` with an argument of an unsupported type will raise an ``++TypeError++``. +Types supported by ``++copy.replace(...)++`` must implement the {object_replacement_protocol}[replace protocol]. -//=== What is the potential impact? +The following built-in types are supported by ``++copy.replace(...)++`` + +* ``++collections.namedtuple()++`` +* ``++dataclasses.dataclass++`` +* ``++datetime.datetime++``, ``++datetime.date++``, ``++datetime.time++`` +* ``++inspect.Signature++``, ``++inspect.Parameter++`` +* ``++types.SimpleNamespace++`` +* https://docs.python.org/3/reference/datamodel.html#code-objects[code objects] == How to fix it -//== How to fix it in FRAMEWORK NAME + +If the argument passed in is a class defined in this project then implementing the {object_replacement_protocol}[replace protocol] by defining the ``++__replace__++`` method. + +[source,python,diff-id=1,diff-type=compliant] +---- +class SomeClass: + def __init__(self, name) + self.name = name + + def __replace__(self, /, **changes) + return SomeClass(changes.get("name", self.name)) +---- === Code examples @@ -18,27 +36,45 @@ FIXME: remove the unused optional headers (that are commented out) [source,python,diff-id=1,diff-type=noncompliant] ---- -FIXME +import copy + +class AClass: + ... + +a = AClass() +b = copy.replace(a) # Noncompliant ---- ==== Compliant solution [source,python,diff-id=1,diff-type=compliant] ---- -FIXME ----- +import copy + +class AClass: + ... + def __replace__(self, /, **changes): + ... + +a = AClass() +b = copy.replace(a) # Compliant -//=== How does this work? -//=== Pitfalls +@dataclass +class ADataClass: + ... + +c = ADataClass() +d = copy.replace(c) # Compliant +---- + +=== Pitfalls -//=== Going the extra mile +Ensure that if the ``++__replace__++`` is implemented that the implementation creates a new object instead of updating the old one. -//== Resources -//=== Documentation -//=== Articles & blog posts -//=== Conference presentations -//=== Standards -//=== External coding guidelines -//=== Benchmarks +== Resources +=== Documentation +* https://docs.python.org/3/library/copy.html#copy.replace +* https://docs.python.org/3/library/copy.html#object.\\__replace__ +* https://docs.python.org/3/whatsnew/3.13.html#copy