Skip to content

Extend patch decorator to add methods to class types #11932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 27, 2024
Merged

Conversation

giograno
Copy link
Member

@giograno giograno commented Nov 26, 2024

Motivation

Our patch decorator easily allows us to patch a class method and extend its behavior.
However, it does not support the case where a given type does not implement a function and we want to add it.
Patch now would support the following use case:

@patch(MyClass)
def new_method(self, *args):
    # some implementation
    ...

c = MyClass()
c.new_method()

If the class already implements a method with the given name, the patch won't have any effect.
Like our current implementation, a patch can be easily undone:

new_method.patch.undo()
c.new_method() # raises AttributeError

Changes

  • Extending patch to support the use case of adding a new function to a class type;
  • Unit tests to verify the behavior of the new use case.

@giograno giograno added the semver: patch Non-breaking changes which can be included in patch releases label Nov 26, 2024
@giograno giograno marked this pull request as ready for review November 26, 2024 15:43
Copy link

github-actions bot commented Nov 26, 2024

LocalStack Community integration with Pro

    2 files  ±0      2 suites  ±0   1h 49m 17s ⏱️ +18s
3 733 tests ±0  3 387 ✅ ±0  346 💤 ±0  0 ❌ ±0 
3 735 runs  ±0  3 387 ✅ ±0  348 💤 ±0  0 ❌ ±0 

Results for commit 014281a. ± Comparison against base commit 0d02e67.

♻️ This comment has been updated with latest results.

Copy link
Member

@thrau thrau left a comment

Choose a reason for hiding this comment

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

nice new feature! just a couple of comments regarding code organization and implementation details

Comment on lines 136 to 138
if inspect.isclass(target):
# Special case: when a class type is passed, we bound the function to the class type patching __getattr__
return Patch._extend_class(target, fn)
Copy link
Member

Choose a reason for hiding this comment

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

i think having this check here doesn't make a ton of sense, since this is explicitly to patch functions. i think this check should be in patch

@@ -111,8 +121,22 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.undo()
return self

@staticmethod
def _extend_class(target: Callable, fn: Callable):
Copy link
Member

Choose a reason for hiding this comment

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

i would put this on the same footing as function

Suggested change
def _extend_class(target: Callable, fn: Callable):
def extend_class(target: Callable, fn: Callable):

Comment on lines 92 to 101
try:
self.old = getattr(self.obj, name)
if self.old and name == "__getattr__":
raise Exception("You can't patch class types implementing __getattr__")
except AttributeError as e:
# When we want to add a function to a class type, we want to intercept this exception and check if the
# name is `__getattr__`.
if name != "__getattr__":
raise e
self.old = None
Copy link
Member

Choose a reason for hiding this comment

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

i think this check should be in apply so we don't cause exceptions while instantiating the object

@giograno giograno requested a review from thrau November 26, 2024 20:34
Copy link
Member

@thrau thrau left a comment

Choose a reason for hiding this comment

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

this is much cleaner, thanks! just one minor nit that i missed, please just amend and merge :-)

@@ -111,6 +119,16 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.undo()
return self

@staticmethod
def extend_class(target: Callable, fn: Callable):
Copy link
Member

Choose a reason for hiding this comment

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

nit: sorry i missed this, but the type hint should by Type, right?

@giograno giograno merged commit 1ff7a48 into master Nov 27, 2024
26 of 28 checks passed
@giograno giograno deleted the extend-patch branch November 27, 2024 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver: patch Non-breaking changes which can be included in patch releases
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants