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

Python upgrade from 3.9.x to 3.12.x #3089

Merged
merged 8 commits into from
Aug 28, 2024

Conversation

mudassir-hafeez
Copy link
Contributor

@mudassir-hafeez mudassir-hafeez commented Aug 6, 2024

IMPORTANT: This PR (Python upgrade) depends on the Webpack upgrade PR. Please review and merge the Webpack upgrade PR first. After it is merged, this PR will be rebased accordingly. Please follow this comment https://github.com/mitodl/hq/issues/5077#issuecomment-2276436824 to get further information.

What are the relevant tickets?

#4965

Description (What does it do?)

This PR upgrades Python to the latest supported version 3.12.4 along with some other dependencies that need to be upgraded to support it.

Screenshots (if appropriate):

  • Desktop screenshots
  • Mobile width screenshots

How can this be tested?

Before getting started, rebuild your containers with docker-compose build web celery to ensure you are working with containers built with the new versions. The entire site should be smoke tested. Here is a basic list, but please add anything else as necessary:

  • Home page and all menus
    • Profile/dashboard/settings
    • sign in/sign out/ create account
    • catalog - Courses/Programs
  • ecommerce/admin/ - Coupon codes/Promo
  • Checkout page
  • Django admin
  • CMS
  • Tasks list: verify that all tasks are working fine
    celery-1 | . courses.tasks.generate_course_certificates
    celery-1 | . courses.tasks.sync_courseruns_data
    celery-1 | . courses.tasks.task_sync_emeritus_course_runs
    celery-1 | . courseware.tasks.change_edx_user_email_async
    celery-1 | . courseware.tasks.change_edx_user_name_async
    celery-1 | . courseware.tasks.create_edx_user_from_id
    celery-1 | . courseware.tasks.create_user_from_id
    celery-1 | . courseware.tasks.repair_faulty_courseware_users
    celery-1 | . courseware.tasks.retry_failed_edx_enrollments
    celery-1 | . hubspot_xpro.tasks.batch_create_hubspot_objects_chunked
    celery-1 | . hubspot_xpro.tasks.batch_update_hubspot_objects_chunked
    celery-1 | . hubspot_xpro.tasks.batch_upsert_associations
    celery-1 | . hubspot_xpro.tasks.batch_upsert_associations_chunked
    celery-1 | . hubspot_xpro.tasks.batch_upsert_hubspot_b2b_deals
    celery-1 | . hubspot_xpro.tasks.batch_upsert_hubspot_b2b_deals_chunked
    celery-1 | . hubspot_xpro.tasks.batch_upsert_hubspot_deals
    celery-1 | . hubspot_xpro.tasks.batch_upsert_hubspot_deals_chunked
    celery-1 | . hubspot_xpro.tasks.batch_upsert_hubspot_objects
    celery-1 | . hubspot_xpro.tasks.sync_b2b_deal_with_hubspot
    celery-1 | . hubspot_xpro.tasks.sync_contact_with_hubspot
    celery-1 | . hubspot_xpro.tasks.sync_deal_with_hubspot
    celery-1 | . hubspot_xpro.tasks.sync_product_with_hubspot
    celery-1 | . sheets.tasks.handle_unprocessed_coupon_requests
    celery-1 | . sheets.tasks.handle_unprocessed_deferral_requests
    celery-1 | . sheets.tasks.handle_unprocessed_refund_requests
    celery-1 | . sheets.tasks.process_coupon_assignment_sheet
    celery-1 | . sheets.tasks.renew_all_file_watches
    celery-1 | . sheets.tasks.renew_file_watch
    celery-1 | . sheets.tasks.schedule_coupon_assignment_sheet_handling
    celery-1 | . sheets.tasks.set_assignment_rows_to_enrolled
    celery-1 | . sheets.tasks.update_incomplete_assignment_delivery_statuses
  • Management commands
  • pytest
  • Editing content and saving it

Additional Context

@arslanashraf7 arslanashraf7 self-assigned this Aug 12, 2024
Copy link
Contributor

@arslanashraf7 arslanashraf7 left a comment

Choose a reason for hiding this comment

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

I requested a couple of changes and some questions. Some change requests would need to be applied overall as I didn't add a separate comment for each of them.

Also, My perception was that we will be doing Webpack upgrade as part of https://github.com/mitodl/hq/issues/5077 separately but this PR contains the code for that as well.

@@ -30,7 +30,7 @@ def process_exception(self, request, exception):
url = self.get_redirect_uri(request, exception)

if url: # noqa: RET503
url += ("?" in url and "&" or "?") + "message={0}&backend={1}".format( # noqa: UP030, UP032
url += ("?" in url and "&" or "?") + "message={}&backend={}".format( # noqa: UP032
Copy link
Contributor

Choose a reason for hiding this comment

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

We can even replace it with f-string

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason pyupgrade did not convert this into an f-string is likely due to its design choice to prioritize readability. According to the pyupgrade documentation, it intentionally avoids converting expressions into f-strings if doing so would make the expression longer or if the substitution parameters are sufficiently complicated, as this can decrease readability.

In this case, the expression might have been considered sufficiently complex, or the transformation into an f-string might have made it less readable, which is why pyupgrade retained the .format() syntax.

However, if you believe an f-string would improve clarity in this case, I'm open to making the change manually. What do you think?

This holds true for similar comments you mentioned regarding f-string where pyupgrade intentionally avoids converting expressions into f-strings.

@@ -57,7 +57,7 @@ def get_user_enrollments(user):
for program_enrollment in program_enrollments
)
)
program_course_ids = set(course.id for course in program_courses) # noqa: C401
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the set might have been intentional here for unique values.

@@ -33,14 +33,14 @@ def mock_cybersource_wsdl(mocked_responses, settings, service_version=SERVICE_VE
Mocks the responses to achieve a functional WSDL
"""
# in order for zeep to load the wsdl, it will load the wsdl and the accompanying xsd definitions
with open(f"{DATA_DIR}/CyberSourceTransaction_{service_version}.wsdl", "r") as wsdl: # noqa: PTH123, UP015
with open(f"{DATA_DIR}/CyberSourceTransaction_{service_version}.wsdl") as wsdl: # noqa: PTH123
Copy link
Contributor

Choose a reason for hiding this comment

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

The file open is readable by default but explicitly adding "r" adds more readability for all code readers. Did Python upgrade complain about it? Same goes for the line below.

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 does not complain but the open() function in Python defaults to read mode ("r"), which pyupgrade makes explicitly specifying "r" to redundant-open-mode/unnecessary and removes it. So we can go any of the following options:

  1. Keep the Explicit "r"
    • We can keep the explicit "r" for readability if that's preferred. Although pyupgrade removed it because it's not necessary, adding it back can make the intent clearer to all code readers, especially those who may not be aware of the default behavior.
  2. Adopt Python's Default Behavior

Note: The first option will be applied consistently to all instances where this change is present and/or explicitly mentioned.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the details you answered my question but maybe inverted, below is why.

It does not complain but the open() function in Python defaults to read mode ("r"), which pyupgrade makes explicitly specifying "r" to redundant-open-mode/unnecessary and removes it. So we can go any of the following options:

I think if pyupgrade complains about it that should mean that the python upgrade does complain. In, which case maybe we can remove the r and add a code comment above this line saying that the file open is read-only by default.

@@ -6,6 +6,7 @@

import factory
import pytest
import pytz
Copy link
Contributor

Choose a reason for hiding this comment

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

We intentionally moved away from pytz as part of #2867. Official docs on https://docs.djangoproject.com/en/5.0/releases/4.0/#what-s-new-in-django-4-0

@@ -283,13 +283,11 @@ def test_course_view( # noqa: PLR0913
class_name = "enrolled"

assert (
f'<a class="enroll-button {class_name}" href="{url}">'.encode("utf-8") # noqa: UP012
in resp.content
f'<a class="enroll-button {class_name}" href="{url}">'.encode() in resp.content
Copy link
Contributor

Choose a reason for hiding this comment

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

Same thing here, Does python upgrade complain about it? explicitly adding utf-8 makes it more readable. Same for the places below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is also made by pyupgrade to encode string to bytes literal as UTF-8 is the default encoding in Python 3. This resolves unnecessary-encode-utf8 (UP012))

Copy link
Contributor

Choose a reason for hiding this comment

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

My answer to this would be similar to #3089 (comment)

@@ -32,8 +32,6 @@
{% endif %}
</script>
{% render_bundle 'style' %}
{% render_bundle 'common' %}
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 needed? Or maybe this is part of Webpack upgrade, If so, we should remove it from this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The changes you mentioned here, along with others related to Webpack are part of the Webpack upgrade. We should first review this PR before proceeding with the Python upgrade, as it depends on the Webpack upgrade and will need to be rebased afterward. For more details, refer to this comment #3089 (comment).

package.json Outdated
Copy link
Contributor

Choose a reason for hiding this comment

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

I think all these changes are Webpack related which we should remove

@@ -823,7 +823,7 @@ def get_desired_coupon_assignments(cls, assignment_rows):
"codes listed in the Sheet. There may be an invalid coupon code in the Sheet."
)
product_coupon_dict = dict(product_coupon_tuples)
return set((row.email, product_coupon_dict[row.code]) for row in valid_rows) # noqa: C401
return {(row.email, product_coupon_dict[row.code]) for row in valid_rows}
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose it would maintain the uniqueness of the items in this set?

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, it will maintain the uniqueness of the items in the set. Set comprehensions, like {(row.email, product_coupon_dict[row.code]) for row in valid_rows}, automatically ensure that the items in the set are unique, just as set() does.
For example:

In [22]: valid_rows = [
    ...:     {"email": "[email protected]", "code": "code1"},
    ...:     {"email": "[email protected]", "code": "code2"},
    ...:     {"email": "[email protected]", "code": "code1"},  # Duplicate
    ...: ]

In [23]: product_coupon_dict = {
    ...:     "code1": "coupon1",
    ...:     "code2": "coupon2",
    ...: }

In [24]: # Using set comprehension
    ...: result_set_comprehension = {(row["email"], product_coupon_dict[row["code"]]) for row in valid_rows}
In [25]: result_set_comprehension
{('[email protected]', 'coupon2'), ('[email protected]', 'coupon1')}

This change was made by pyupgrade and is supported by Python’s style guidelines for clarity and performance. It also resolves the unnecessary-generator-set (C401) issue.

This holds true for similar set comprehensions you mentioned regarding uniqueness.

Copy link
Contributor

@arslanashraf7 arslanashraf7 Aug 15, 2024

Choose a reason for hiding this comment

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

Yes, it will maintain the uniqueness of the items in the set.

Thanks. This answers my question.

@@ -444,7 +444,7 @@ def process_row(self, row_index, row_data):
row_db_record=coupon_gen_request,
row_object=None,
result_type=ResultType.FAILED,
message="Parsing failure: {}".format(str(exc)), # noqa: UP032
message=f"Parsing failure: {exc!s}",
Copy link
Contributor

Choose a reason for hiding this comment

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

I think str would be called by default

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think str would be called by default

Yes, in many cases, calling str() within an f-string is unnecessary and can be removed entirely, as the value will be converted to a string automatically. But, pyupgrade keeps explicit type conversions like f"Parsing failure: {str(exc)}", which pre-commit further resolves to f"Parsing failure: {exc!s}" for rules like RUF010.

This comment is valid for the overall comment you mentioned regarding the str.

@@ -134,7 +134,7 @@ def process_row( # noqa: C901, PLR0911
row_db_record=deferral_request,
row_object=None,
result_type=ResultType.FAILED,
message="Parsing failure: {}".format(str(exc)), # noqa: UP032
message=f"Parsing failure: {exc!s}",
Copy link
Contributor

Choose a reason for hiding this comment

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

Some similar comments about this above

@mudassir-hafeez
Copy link
Contributor Author

I requested a couple of changes and some questions. Some change requests would need to be applied overall as I didn't add a separate comment for each of them.

I’ve left comments on specific questions and will continue to add more as needed. Some of these comments address similar requests or questions.

Also, My perception was that we will be doing Webpack upgrade as part of https://github.com/mitodl/hq/issues/5077 separately but this PR contains the code for that as well.

I’ve mentioned the review order for these PRs in the ticket here and referred in this PR description . We should first review the Webpack upgrade PR before proceeding with this PR (Python upgrade), as it depends on the Webpack upgrade and will need to be rebased afterward. I will apply a "on hold" label to this PR until the Webpack upgrade PR is reviewed and merged.

@arslanashraf7
Copy link
Contributor

I requested a couple of changes and some questions. Some change requests would need to be applied overall as I didn't add a separate comment for each of them.

I’ve left comments on specific questions and will continue to add more as needed. Some of these comments address similar requests or questions.

I responded to them.

Also, My perception was that we will be doing Webpack upgrade as part of mitodl/hq#5077 separately but this PR contains the code for that as well.

I’ve mentioned the review order for these PRs in the ticket here and referred in this PR description . We should first review the Webpack upgrade PR before proceeding with this PR (Python upgrade), as it depends on the Webpack upgrade and will need to be rebased afterward. I will apply a "on hold" label to this PR until the Webpack upgrade PR is reviewed and merged.

Sorry, I didn't read the additional context because I didn't feel like this might have one. Thanks for following up on the call and adding the On Hold label. But this PR is more like blocked

Ideally in cases like you mentioned, we can follow the approach I'm mentioning below because that adds clarity:

  1. If you are looking for PR#A to be reviewed before PR#B you should not mark PR#B as Needs Review you should mark it Blocked with a comment about why it is blocked
  2. Critical information, such as the order of PR review, can be mentioned at the top of the PR description so that the reviewer would not wander off track.

@arslanashraf7
Copy link
Contributor

arslanashraf7 commented Aug 27, 2024

@mudassir-hafeez It is not blocked anymore. correct? Could you rebase the PR? There are conflicts.

@mudassir-hafeez mudassir-hafeez force-pushed the mudassir/python-upgrade-3.12 branch from f3c04e9 to 9577311 Compare August 27, 2024 10:00
@mudassir-hafeez mudassir-hafeez force-pushed the mudassir/python-upgrade-3.12 branch from 850ea97 to f2895b6 Compare August 27, 2024 10:05
@mudassir-hafeez
Copy link
Contributor Author

@mudassir-hafeez It is not blocked anymore. correct? Could you rebase the PR? There are conflicts.

Yes, it's no longer blocked. I've rebased the PR, resolved the conflicts, and tested it. It should be good to proceed now.

Copy link
Contributor

@arslanashraf7 arslanashraf7 left a comment

Choose a reason for hiding this comment

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

👍

@mudassir-hafeez mudassir-hafeez merged commit c0ea374 into master Aug 28, 2024
7 checks passed
@mudassir-hafeez mudassir-hafeez deleted the mudassir/python-upgrade-3.12 branch August 28, 2024 06:27
This was referenced Aug 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants