Skip to content
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

refactor: contexts and builders #579

Merged
merged 4 commits into from
Aug 6, 2024

Conversation

giovanni-guidini
Copy link
Contributor

introduces the NotificationContext and ContextBuilder classes.
the only specialization currently is for the PR comment.

the context builders generate the context details necessary to create and
send the message later on. Each step is called in order, and if one step fails
we bail with an exception. This means the notification can't be sent.

Copy link

codecov bot commented Jul 31, 2024

Codecov Report

Attention: Patch coverage is 99.70149% with 1 line in your changes missing coverage. Please review.

Project coverage is 97.62%. Comparing base (f236938) to head (f4f8e97).
Report is 3 commits behind head on main.

Changes have been made to critical files, which contain lines commonly executed in production. Learn more

✅ All tests successful. No failed tests found.

Files Patch % Lines
...ces/bundle_analysis/new_notify/contexts/comment.py 98.48% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #579      +/-   ##
==========================================
+ Coverage   97.57%   97.62%   +0.05%     
==========================================
  Files         455      460       +5     
  Lines       36440    37074     +634     
==========================================
+ Hits        35556    36195     +639     
+ Misses        884      879       -5     
Flag Coverage Δ
integration 97.55% <99.70%> (+0.02%) ⬆️
latest-uploader-overall 97.55% <99.70%> (+0.02%) ⬆️
unit 97.55% <99.70%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 94.90% <99.29%> (+0.18%) ⬆️
OutsideTasks 97.80% <99.70%> (+0.04%) ⬆️
Files Coverage Δ
services/bundle_analysis/new_notify/conftest.py 100.00% <100.00%> (ø)
...es/bundle_analysis/new_notify/contexts/__init__.py 100.00% <100.00%> (ø)
...nalysis/new_notify/contexts/tests/test_contexts.py 100.00% <100.00%> (ø)
...ces/bundle_analysis/new_notify/contexts/comment.py 98.48% <98.48%> (ø)

... and 6 files with indirect coverage changes

This change has been scanned for critical changes. Learn more

@codecov-qa
Copy link

codecov-qa bot commented Jul 31, 2024

Codecov Report

Attention: Patch coverage is 99.70149% with 1 line in your changes missing coverage. Please review.

Project coverage is 97.55%. Comparing base (f236938) to head (f4f8e97).
Report is 3 commits behind head on main.

✅ All tests successful. No failed tests found.

Files Patch % Lines
...ces/bundle_analysis/new_notify/contexts/comment.py 98.48% 1 Missing ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #579      +/-   ##
==========================================
+ Coverage   97.53%   97.55%   +0.02%     
==========================================
  Files         420      425       +5     
  Lines       35234    35593     +359     
==========================================
+ Hits        34364    34724     +360     
+ Misses        870      869       -1     
Flag Coverage Δ
integration 97.55% <99.70%> (+0.02%) ⬆️
latest-uploader-overall 97.55% <99.70%> (+0.02%) ⬆️
unit 97.55% <99.70%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 94.70% <99.29%> (+0.06%) ⬆️
OutsideTasks 97.80% <99.70%> (+0.04%) ⬆️
Files Coverage Δ
services/bundle_analysis/new_notify/conftest.py 100.00% <100.00%> (ø)
...es/bundle_analysis/new_notify/contexts/__init__.py 100.00% <100.00%> (ø)
...nalysis/new_notify/contexts/tests/test_contexts.py 100.00% <100.00%> (ø)
...ces/bundle_analysis/new_notify/contexts/comment.py 98.48% <98.48%> (ø)

... and 6 files with indirect coverage changes

@codecov-notifications
Copy link

codecov-notifications bot commented Jul 31, 2024

Codecov Report

Attention: Patch coverage is 99.70149% with 1 line in your changes missing coverage. Please review.

✅ All tests successful. No failed tests found.

Files Patch % Lines
...ces/bundle_analysis/new_notify/contexts/comment.py 98.48% 1 Missing ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #579      +/-   ##
==========================================
+ Coverage   97.50%   97.55%   +0.05%     
==========================================
  Files         414      425      +11     
  Lines       35002    35593     +591     
==========================================
+ Hits        34127    34724     +597     
+ Misses        875      869       -6     
Flag Coverage Δ
integration 97.55% <99.70%> (+0.05%) ⬆️
latest-uploader-overall 97.55% <99.70%> (+0.05%) ⬆️
unit 97.55% <99.70%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 94.70% <97.70%> (+0.15%) ⬆️
OutsideTasks 97.80% <99.40%> (+0.07%) ⬆️
Files Coverage Δ
services/bundle_analysis/new_notify/conftest.py 100.00% <100.00%> (ø)
...es/bundle_analysis/new_notify/contexts/__init__.py 100.00% <100.00%> (ø)
...nalysis/new_notify/contexts/tests/test_contexts.py 100.00% <100.00%> (ø)
...ces/bundle_analysis/new_notify/contexts/comment.py 98.48% <98.48%> (ø)

... and 7 files with indirect coverage changes

Copy link

codecov-public-qa bot commented Jul 31, 2024

Codecov Report

Attention: Patch coverage is 99.70149% with 1 line in your changes missing coverage. Please review.

Project coverage is 97.55%. Comparing base (f236938) to head (f4f8e97).
Report is 3 commits behind head on main.

✅ All tests successful. No failed tests found.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #579      +/-   ##
==========================================
+ Coverage   97.53%   97.55%   +0.02%     
==========================================
  Files         420      425       +5     
  Lines       35234    35593     +359     
==========================================
+ Hits        34364    34724     +360     
+ Misses        870      869       -1     
Flag Coverage Δ
integration 97.55% <99.70%> (+0.02%) ⬆️
latest-uploader-overall 97.55% <99.70%> (+0.02%) ⬆️
unit 97.55% <99.70%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
NonTestCode 94.70% <99.29%> (+0.06%) ⬆️
OutsideTasks 97.80% <99.70%> (+0.04%) ⬆️
Files Coverage Δ
services/bundle_analysis/new_notify/conftest.py 100.00% <100.00%> (ø)
...es/bundle_analysis/new_notify/contexts/__init__.py 100.00% <100.00%> (ø)
...nalysis/new_notify/contexts/tests/test_contexts.py 100.00% <100.00%> (ø)
...ces/bundle_analysis/new_notify/contexts/comment.py 98.48% <98.48%> (ø)

... and 6 files with indirect coverage changes

@giovanni-guidini giovanni-guidini force-pushed the gio/refactor/ba-notifier/helpers branch 2 times, most recently from d335c05 to a22bdab Compare August 1, 2024 09:57
Base automatically changed from gio/refactor/ba-notifier/helpers to main August 1, 2024 10:18
@giovanni-guidini giovanni-guidini force-pushed the gio/refactor/ba-notifier/contexts branch from e7eee09 to 1a378e4 Compare August 1, 2024 12:13
introduces the NotificationContext and ContextBuilder classes.
the only specialization currently is for the PR comment.

the context builders generate the context details necessary to create and
send the message later on. Each step is called in order, and if one step fails
we bail with an exception. This means the notification can't be sent.
class NotificationContextBuilder:
"""Creates the BaseBundleAnalysisNotificationContext one step at a time, in the correct order."""

# TODO: Find a way to re-use the base-context (by cloning it instead of recreating it for every notification)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we try a singleton pattern approach perhaps? Make it once, save it, and if it exists already, use it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Singleton I don't think was the answer.
I did something tho, in which you can initialize the builder from an existing context by looking at what fields the existing context has and porting them over to the new one.

Because notification is a read-only process it's OK to have the same reference in multiple places.

Copy link
Contributor

Choose a reason for hiding this comment

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

Huh, does this have a pattern?? Not really familiar w/ this

log = logging.getLogger(__name__)


class BundleAnalysisCommentNotificationContext(BaseBundleAnalysisNotificationContext):
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: since we call the notification type PR_COMMENT, would it make sense to call this BundleAnalysisPRCommentNotificationContext comment instead?

)
return self
comparison = self._notification_context.bundle_analysis_comparison
should_continue = {
Copy link
Contributor

Choose a reason for hiding this comment

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

I found this a bit hard to read at first - the False: True broke my brain 😂, but after going through it see why you did it this way. Perhaps a case statement could help with readability, I trust your judgement 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's funny you say that because I was originally going to go with a match statement. But you can't directly assign to a variable using that.

Looking online some stack overflow question suggested this pattern and I was like "ok cool"

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeaah I suppose you'd have to write the return statement in each case instead of assigning a variable. We can leave it as is

Create a descriptor for notification context fields so that we fail with a
custom exception if that field was not loaded but we are trying to access it.

With the added benefit of reducing the amount of code a little bit :E

I added typehints to the fields (e.g. `pull: EnrichedPull = NotificationContextField[EnrichedPull]()`) because my VSCode langauge server was not being helpful with the generics... but leaving the generics in any case.
@giovanni-guidini giovanni-guidini force-pushed the gio/refactor/ba-notifier/contexts branch from b72165c to 670dd89 Compare August 2, 2024 06:32
This lets us only load the necessary info once and re-use the calculations for subsequent contexts that need to be built.
Copy link
Contributor

@matt-codecov matt-codecov left a comment

Choose a reason for hiding this comment

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

didn't review the tests, but this is clean and i can see how this would be used for our other notification paths. great work!

pass


class NotificationContextField(Generic[T]):
Copy link
Contributor

Choose a reason for hiding this comment

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

ahh python descriptors, i remember you

this appears to be a very straightforward use of the pattern, but could you add a doc comment saying saying, like

NotificationContextField is a descriptor akin to a Django model field. If you create one as a class member named foo, it will define the behavior to get and set an instance member named foo.

just to help out folks who can't recognize the pattern on sight (i had to google around to remember)

notification_context = builder.build_context().get_result()
"""

notification_type: NotificationType
Copy link
Contributor

Choose a reason for hiding this comment

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

is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes... I think to notify when the context fails to build. Might be in a later PR

Comment on lines 163 to 168
def build_context(self) -> "BundleAnalysisCommentContextBuilder":
super().build_context()
async_to_sync(self.load_enriched_pull)()
self.load_bundle_comparison()
self.evaluate_has_enough_changes()
return self
Copy link
Contributor

Choose a reason for hiding this comment

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

all of these functions return self, but you're not really using that. also, none of them take arguments. every creation of a NotificationContext is going to go the same way. so you don't really need the builder pattern exactly

you could probably just do all this in a constructor?

class BaseBundleAnalysisNotificationContext():
    def __init__(self, commit: Commit, current_yaml: UserYaml, gh_app_installation_name: str) -> None:
        self.commit = commit
        self.current_yaml = current_yaml
        self.gh_app_installation_name = gh_app_installation_name

        self.load_commit_report()
        self.load_bundle_analysis_report()

    def load_commit_report(self):
        pass
    def load_bundle_analysis_report(self):
        pass

class BundleAnalysisCommentNotificationContext(BaseBundleAnalysisNotificationContext):
    def __init__(self, commit: Commit, current_yaml: UserYaml, gh_app_installation_name: str) -> None:
        super(BaseBundleAnalysisNotificationContext, self).__init__()
        async_to_sync(self.load_enriched_pull)()
        self.load_bundle_comparison()
        self.evaluate_has_enough_changes()

    async def load_enriched_pull(self):
        pass
    def load_bundle_comparison(self):
        pass
    def evaluate_has_enough_changes(self):
        pass

though i guess that makes it hard to test the individual member functions. maybe keep the builder then. idk

Copy link
Contributor Author

@giovanni-guidini giovanni-guidini Aug 6, 2024

Choose a reason for hiding this comment

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

so you don't really need the builder pattern exactly

You're probably right... I do want to avoid the load function being declared in the Context though... I would prefer to have the context be more similar to a dataclass.

I think using composition for the load steps (they are declared in different classes that are composed into the context) and the context itself declared the build steps might reduce the amount of code we have and overall be more flexible and easier to reuse steps

And we get rid of the descriptor too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe I stop messing with it and we see how this feels. It feels a clearer interface in any case so it's an improvement

* improve docstrings
* change bundle analysis PR comment context name
@giovanni-guidini giovanni-guidini added this pull request to the merge queue Aug 6, 2024
Merged via the queue into main with commit c864a13 Aug 6, 2024
25 of 26 checks passed
@giovanni-guidini giovanni-guidini deleted the gio/refactor/ba-notifier/contexts branch August 6, 2024 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants