-
Notifications
You must be signed in to change notification settings - Fork 13
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
Wraps transaction into separate class #213
Wraps transaction into separate class #213
Conversation
62c5503
to
6d98695
Compare
6d98695
to
e8038fa
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking care of this refactor! I enjoy the split in ReadOnly
and Writable
and I think we should keep it.
Before merging this PR, I think we should make the context more pythonic. To me, we can define a single entry point à la open()
or ZipFile()
.
I'm thinking of something like this:
# Check rollout
with GooglePlay(package_name, service_account, credentials_file) as google_play:
for (release, age) in check_rollout(google_play, config.days):
# ...
# Push apk
with GooglePlay(
package_name, service_account, credentials_file,
mode='w', commit=commit, contact_google_play=contact_google_play
) as google_play:
for path, metadata in apks_metadata_per_paths.items():
# ...
@contextmanager
def GooglePlay(package_name, service_account=None, credentials_file=None, mode='r', commit=False, contact_google_play=True):
if contact_google_play:
if not service_account or not credentials_file:
raise ValueError()
edit_service = _connect(service_account, credentials_file)
else:
edit_service = _MockService()
if mode == 'r':
google_play = _ReadOnlyGooglePlay(edit_service, package_name)
elif mode == 'w':
google_play = _WritableGooglePlay(edit_service, package_name)
else:
raise ValueError()
yield google_play
if commit:
edit_service.commit()
else:
logger.warning()
This way, consumers of the GooglePlay context don't have to deal with multiple calls to get the right context initialized. What do you think?
I'm not too sure what's the end result is going to look like. That's why I don't r+ yet, I'd like to see it
mozapkpublisher/push_apk.py
Outdated
for path, metadata in apks_metadata_per_paths.items(): | ||
edit_service.upload_apk(path) | ||
with WritableGooglePlay.transaction(connection, package_name, | ||
do_not_commit=not commit) as google_play: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Let's avoid negative boolean names. It adds a mental layer of indirection when reading the code.
Hey, thanks for the thoughtful response :) A couple thoughts:
This is for a couple reasons:
One other note is that, if at some point in the future, the two implementations require a different set of parameters (perhaps
and I'll add a This way, it's more like
|
🤔 Looking at this a little further, I'm not sure it makes sense to remove the static function for each of the However, there is a set-up sequence, where a connection is used to establish a transaction. Importantly, this operation is a "side-effect"-y operation, so it doesn't make sense to do in the constructor.
And, once we have this function that is closely related to 🤔 That's the costs and benefits affecting my design decisions so far, what do you think? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: @mitchhentges and I have had a lengthy conversation since last comment, about classes vs raw types, which led to mozilla-releng/releng-rfcs#29.
I've tried to review this PR by not being biased about my general position on that matter, and by just reflecting upon calls that occur in this changeset.
I'm okay to not have the Read/Write split anymore. If the needs comes up, we can bring it back later.
I think some changes are required before landing the PR. I'd like to get another view them once they're done.
Dropping some initial thoughts on this. To be honest, I like both options, for different reasons. But overall, I'm slightly leaning towards the pythonic way of doing it, rather than classes. Reasons I like the using classes approach:
Reasons I like the pythonic way:
With all that said, I still believe consistency is good across an organization so I'm leaning towards the pythonic way of overloading multiple behaviors within the primitive functions. |
4ce8698
to
42a35f9
Compare
9d63dc5
to
2018fd5
Compare
2018fd5
to
592768e
Compare
Thanks Mihai for weighing in on the discussion. Though I'm not super hyped on the result, I'm glad that we can unblock this PR and move forward again 😄
Classes aren't less pythonic 🤔 However, in an API boundary with a known set of possible data, it's now possible to represent your data in an explicit structure.
Yes, it's dynamically typed, but Python is giving us the built-in, native ability to structure known types of data. An argument analogous to this is "computers will always remain operating on primitive processor instructions, so we should program at that primitive level accordingly."
Using classes to encapsulate logic and config has been possible since before the release of the first Python 3. They aren't experimental at this point.
The amount of weight that this point appears to have has surprised me. Mihai and Johan, you guys are pretty aligned on the direction here, so I'll 🤷♂️. I've updated the PR to use raw primitives and removed the I'm not confident in the direction that we'll take, but I'm really happy to be able to have a clear direction for our team that I can implement this PR against so we can have it landed 😃 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @mitchhentges for accepting to implement Mihai's and my preferred way. I appreciate this gesture.
The code looks good to me. I found one small nit. Let's address it before landing. No need for me to have another look 🙂
This breaks apart
EditService
:with WritableGooglePlay.transaction() as google_play:
GooglePlayConnection
abstraction@JohanLorenzo I'm pretty happy with these changes, though I'm on the fence about splitting
ReadOnlyGooglePlay
fromWritableGooglePlay
. My main reasoning for doing happened when I saw this:When you're doing work that just requires read-only access, it's noisy to add
do_not_commit=True
, and setting up the wholewith
statement is annoying. I'm especially worried that it will be too easy to omit thedo_not_commit
parameter, causing read-only work to take longer, since an unnecessary commit happens.So, by splitting the read-only class from the writable one, we get:
Devs will realize the existence of
ReadOnlyGooglePlay
(if there's aWriteableGooglePlay
, there's probably a "read"-related one), and will opt to use it if possible, since its less verbose to write. I think this is way better than having to scan for missingdo_not_commit
parameters or doing unnecessary commits. What do you think?