From cbd6564edd5cf4a8239e1ea1e0f2be00f9e0e3c5 Mon Sep 17 00:00:00 2001 From: Seppli11 Date: Tue, 12 Nov 2024 11:02:20 +0000 Subject: [PATCH 1/3] Create rule S7156 --- rules/S7156/metadata.json | 2 ++ rules/S7156/python/metadata.json | 25 ++++++++++++++++++ rules/S7156/python/rule.adoc | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 rules/S7156/metadata.json create mode 100644 rules/S7156/python/metadata.json create mode 100644 rules/S7156/python/rule.adoc diff --git a/rules/S7156/metadata.json b/rules/S7156/metadata.json new file mode 100644 index 00000000000..2c63c085104 --- /dev/null +++ b/rules/S7156/metadata.json @@ -0,0 +1,2 @@ +{ +} diff --git a/rules/S7156/python/metadata.json b/rules/S7156/python/metadata.json new file mode 100644 index 00000000000..e8545bbc3db --- /dev/null +++ b/rules/S7156/python/metadata.json @@ -0,0 +1,25 @@ +{ + "title": "FIXME", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-7156", + "sqKey": "S7156", + "scope": "All", + "defaultQualityProfiles": ["Sonar way"], + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "HIGH", + "RELIABILITY": "MEDIUM", + "SECURITY": "LOW" + }, + "attribute": "CONVENTIONAL" + } +} diff --git a/rules/S7156/python/rule.adoc b/rules/S7156/python/rule.adoc new file mode 100644 index 00000000000..caae0d69054 --- /dev/null +++ b/rules/S7156/python/rule.adoc @@ -0,0 +1,44 @@ +FIXME: add a description + +// If you want to factorize the description uncomment the following line and create the file. +//include::../description.adoc[] + +== Why is this an issue? + +FIXME: remove the unused optional headers (that are commented out) + +//=== What is the potential impact? + +== How to fix it +//== How to fix it in FRAMEWORK NAME + +=== Code examples + +==== Noncompliant code example + +[source,python,diff-id=1,diff-type=noncompliant] +---- +FIXME +---- + +==== Compliant solution + +[source,python,diff-id=1,diff-type=compliant] +---- +FIXME +---- + +//=== How does this work? + +//=== Pitfalls + +//=== Going the extra mile + + +//== Resources +//=== Documentation +//=== Articles & blog posts +//=== Conference presentations +//=== Standards +//=== External coding guidelines +//=== Benchmarks From 1a20440092c4715c63d2efec0f20fddccc82a4ce Mon Sep 17 00:00:00 2001 From: Sebastian Zumbrunn Date: Tue, 12 Nov 2024 15:04:17 +0100 Subject: [PATCH 2/3] write initial version of S7156 --- rules/S7156/python/metadata.json | 10 ++--- rules/S7156/python/rule.adoc | 74 +++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 26 deletions(-) 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..58067db74b0 100644 --- a/rules/S7156/python/rule.adoc +++ b/rules/S7156/python/rule.adoc @@ -1,16 +1,32 @@ -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]. + +The following built-in types are supported by ``++copy.replace(...)++`` -//=== What is the potential impact? +* ``++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 +34,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): + ... -//=== How does this work? +a = AClass() +b = copy.replace(a) # Compliant + + +@dataclass +class ADataClass: + ... + +c = ADataClass() +d = copy.replace(c) # Compliant +---- -//=== Pitfalls +=== 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 From 04f68e935a0f046c3b89be85cfb2ea2747016647 Mon Sep 17 00:00:00 2001 From: Sebastian Zumbrunn Date: Wed, 13 Nov 2024 13:51:36 +0100 Subject: [PATCH 3/3] fix after review --- rules/S7156/python/rule.adoc | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/rules/S7156/python/rule.adoc b/rules/S7156/python/rule.adoc index 58067db74b0..d7f928b2e8a 100644 --- a/rules/S7156/python/rule.adoc +++ b/rules/S7156/python/rule.adoc @@ -1,30 +1,37 @@ :object_replacement_protocol: https://docs.python.org/3/library/copy.html#object.__replace__ +This rule raises an issue when ``++copy.replace++`` is used on an incorrect type. + == Why is this an issue? -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]. +Python 3.13 introduced the function ``++copy.replace(obj, /, **changes)++`` which creates a new duplicate of the same type as ``++obj++`` then updating the values of the fields provided in ``++changes++``. +However, calling ``++copy.replace(...)++`` with an argument of an unsupported type will raise an ``++TypeError++``. +In order for a type to be supported by ``++copy.replace(...)++``, the {object_replacement_protocol}[replace protocol] must be implemented. -The following built-in types are supported by ``++copy.replace(...)++`` +The following built-in types implement the {object_replacement_protocol}[replace protocol] and are thus 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/library/typing.html#typing.NamedTuple[typed named tuples], ``++collections.namedtuple()++`` +* https://docs.python.org/3/library/dataclasses.html[data classes] * https://docs.python.org/3/reference/datamodel.html#code-objects[code objects] +=== Exceptions + +This issue is only raised for Python 3.13 and above, since the respective method isn't available in previous versions of Python. + == How to fix it -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. +If the argument passed to ``++copy.replace(...)++`` is a class defined in this project then, to fix the issue, implement the {object_replacement_protocol}[replace protocol] by defining the ``++__replace__++`` method in the class. [source,python,diff-id=1,diff-type=compliant] ---- class SomeClass: - def __init__(self, name) + def __init__(self, name): self.name = name - def __replace__(self, /, **changes) + def __replace__(self, /, **changes): return SomeClass(changes.get("name", self.name)) ---- @@ -32,7 +39,7 @@ class SomeClass: ==== Noncompliant code example -[source,python,diff-id=1,diff-type=noncompliant] +[source,python,diff-id=2,diff-type=noncompliant] ---- import copy @@ -40,12 +47,12 @@ class AClass: ... a = AClass() -b = copy.replace(a) # Noncompliant +b = copy.replace(a) # Noncompliant: AClass does not implement __replace__(...) ---- ==== Compliant solution -[source,python,diff-id=1,diff-type=compliant] +[source,python,diff-id=2,diff-type=compliant] ---- import copy @@ -73,6 +80,6 @@ Ensure that if the ``++__replace__++`` is implemented that the implementation cr == 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 +* Python Documentation - https://docs.python.org/3/library/copy.html#copy.replace[copy — Shallow and deep copy operations — copy.replace] +* Python Documentation - {object_replacement_protocol}[copy — Shallow and deep copy operations — object.\\__replace__] +* Python Documentation - https://docs.python.org/3/whatsnew/3.13.html#copy[What's New in Python 3.13]