Skip to content

Commit

Permalink
cleanup references of python 2 & <3.11 (#35799)
Browse files Browse the repository at this point in the history
* chore: cleanup of old python references
  • Loading branch information
irtazaakram authored Nov 15, 2024
1 parent 9de9f26 commit ec2a698
Show file tree
Hide file tree
Showing 18 changed files with 87 additions and 165 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/verify-dunder-init.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI
name: Verify Dunder __init__.py Files

on:
pull_request:
Expand Down
60 changes: 22 additions & 38 deletions common/djangoapps/student/tests/test_linkedin.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,20 @@ class LinkedInAddToProfileUrlTests(TestCase):
def test_linked_in_url(self, cert_mode, expected_cert_name):
config = LinkedInAddToProfileConfigurationFactory()

# We can switch to this once edx-platform reaches Python 3.8
# expected_url = (
# 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
# 'name={platform}+{cert_name}&certUrl={cert_url}&'
# 'organizationId={company_identifier}'
# ).format(
# platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
# cert_name=expected_cert_name,
# cert_url=quote(self.CERT_URL, safe=''),
# company_identifier=config.company_identifier,
# )
expected_url = (
'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
'name={platform}+{cert_name}&certUrl={cert_url}&'
'organizationId={company_identifier}'
).format(
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
cert_name=expected_cert_name,
cert_url=quote(self.CERT_URL, safe=''),
company_identifier=config.company_identifier,
)

actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL)

# We can switch to this instead of the assertIn once edx-platform reaches Python 3.8
# There was a problem with dict ordering in the add_to_profile_url function that will go away then.
# self.assertEqual(actual_url, expected_url)

assert 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME' in actual_url
assert f'&name={quote(settings.PLATFORM_NAME.encode("utf-8"))}+{expected_cert_name}' in actual_url
assert '&certUrl={cert_url}'.format(cert_url=quote(self.CERT_URL, safe='')) in actual_url
assert f'&organizationId={config.company_identifier}' in actual_url
self.assertEqual(actual_url, expected_url)

@ddt.data(
('honor', 'Honor+Code+Credential+for+Test+Course+%E2%98%83'),
Expand All @@ -72,26 +64,18 @@ def test_linked_in_url(self, cert_mode, expected_cert_name):
def test_linked_in_url_with_cert_name_override(self, cert_mode, expected_cert_name):
config = LinkedInAddToProfileConfigurationFactory()

# We can switch to this once edx-platform reaches Python 3.8
# expected_url = (
# 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
# 'name={platform}+{cert_name}&certUrl={cert_url}&'
# 'organizationId={company_identifier}'
# ).format(
# platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
# cert_name=expected_cert_name,
# cert_url=quote(self.CERT_URL, safe=''),
# company_identifier=config.company_identifier,
# )
expected_url = (
'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
'name={platform}+{cert_name}&certUrl={cert_url}&'
'organizationId={company_identifier}'
).format(
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
cert_name=expected_cert_name,
cert_url=quote(self.CERT_URL, safe=''),
company_identifier=config.company_identifier,
)

with with_site_configuration_context(configuration=self.SITE_CONFIGURATION):
actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL)

# We can switch to this instead of the assertIn once edx-platform reaches Python 3.8
# There was a problem with dict ordering in the add_to_profile_url function that will go away then.
# self.assertEqual(actual_url, expected_url)

assert 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME' in actual_url
assert f'&name={quote(settings.PLATFORM_NAME.encode("utf-8"))}+{expected_cert_name}' in actual_url
assert '&certUrl={cert_url}'.format(cert_url=quote(self.CERT_URL, safe='')) in actual_url
assert f'&organizationId={config.company_identifier}' in actual_url
self.assertEqual(actual_url, expected_url)
33 changes: 12 additions & 21 deletions common/djangoapps/student/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,28 +440,19 @@ def test_linked_in_add_to_profile_btn_with_certificate(self):
assert response.status_code == 200
self.assertContains(response, 'Add Certificate to LinkedIn')

# We can switch to this and the commented out assertContains once edx-platform reaches Python 3.8
# expected_url = (
# 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
# 'name={platform}+Honor+Code+Certificate+for+Omega&certUrl={cert_url}&'
# 'organizationId={company_identifier}'
# ).format(
# platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
# cert_url=quote(cert.download_url, safe=''),
# company_identifier=linkedin_config.company_identifier,
# )

# self.assertContains(response, escape(expected_url))

# These can be removed (in favor of the above) once we are on Python 3.8. Fails in 3.5 because of dict ordering
self.assertContains(response, escape('https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME'))
self.assertContains(response, escape('&name={platform}+Honor+Code+Certificate+for+Omega'.format(
platform=quote(settings.PLATFORM_NAME.encode('utf-8'))
)))
self.assertContains(response, escape('&certUrl={cert_url}'.format(cert_url=quote(cert.download_url, safe=''))))
self.assertContains(response, escape('&organizationId={company_identifier}'.format(
expected_url = (
'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
'name={platform}+Honor+Code+Certificate+for+Omega&'
'certUrl={cert_url}&'
'organizationId={company_identifier}'
).format(
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
cert_url=quote(cert.download_url, safe=''),
company_identifier=linkedin_config.company_identifier
)))
)

# Single assertion for the expected LinkedIn URL
self.assertContains(response, escape(expected_url))

@skip_unless_lms
def test_dashboard_metadata_caching(self):
Expand Down
24 changes: 9 additions & 15 deletions common/djangoapps/util/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,38 +53,32 @@ def wrapper(request, *args, **kwargs):
# specifically the branding index, to do authentication.
# If that page is cached the authentication doesn't
# happen, so we disable the cache when that feature is enabled.
if (
not request.user.is_authenticated
):
if not request.user.is_authenticated:
# Use the cache. The same view accessed through different domain names may
# return different things, so include the domain name in the key.
domain = str(request.META.get('HTTP_HOST')) + '.'
cache_key = domain + "cache_if_anonymous." + get_language() + '.' + request.path
domain = request.META.get('HTTP_HOST', '') + '.'
cache_key = f"{domain}cache_if_anonymous.{get_language()}.{request.path}"

# Include the values of GET parameters in the cache key.
for get_parameter in get_parameters:
parameter_value = request.GET.get(get_parameter)
if parameter_value is not None:
# urlencode expects data to be of type str, and doesn't deal well with Unicode data
# since it doesn't provide a way to specify an encoding.
cache_key = cache_key + '.' + urlencode({
get_parameter: str(parameter_value).encode('utf-8')
})
cache_key += '.' + urlencode({get_parameter: str(parameter_value).encode('utf-8')})

response = cache.get(cache_key)

if response:
# A hack to ensure that the response data is a valid text type for both Python 2 and 3.
response_content = list(response._container) # lint-amnesty, pylint: disable=bad-option-value, protected-access, protected-member
response.content = b''
for item in response_content:
response.write(item)
# Ensure that response content is properly handled for caching
response.content = (
# pylint: disable=protected-access
b''.join(response._container) if hasattr(response, '_container') else response.content
)
else:
response = view_func(request, *args, **kwargs)
cache.set(cache_key, response, 60 * 3)

return response

else:
# Don't use the cache.
return view_func(request, *args, **kwargs)
Expand Down
10 changes: 2 additions & 8 deletions lms/djangoapps/learner_dashboard/tests/test_programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,9 @@ def program_sort_key(cls, program):
def assert_dict_contains_subset(self, superset, subset):
"""
Verify that the dict superset contains the dict subset.
Works like assertDictContainsSubset, deprecated since Python 3.2.
See: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertDictContainsSubset.
"""
superset_keys = set(superset.keys())
subset_keys = set(subset.keys())
intersection = {key: superset[key] for key in superset_keys & subset_keys}

assert subset == intersection
for key, value in subset.items():
assert key in superset and superset[key] == value, f"{key}: {value} not found in superset or does not match"

def test_login_required(self, mock_get_programs):
"""
Expand Down
9 changes: 6 additions & 3 deletions openedx/core/djangoapps/content/search/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,12 @@ def meili_id_from_opaque_key(usage_key: UsageKey) -> str:
we could use PublishableEntity's primary key / UUID instead.
"""
# The slugified key _may_ not be unique so we append a hashed string to make it unique:
key_bin = str(usage_key).encode()
suffix = blake2b(key_bin, digest_size=4).hexdigest() # When we use Python 3.9+, should add usedforsecurity=False
return slugify(str(usage_key)) + "-" + suffix
key_str = str(usage_key)
key_bin = key_str.encode()

suffix = blake2b(key_bin, digest_size=4, usedforsecurity=False).hexdigest()

return f"{slugify(key_str)}-{suffix}"


def _meili_access_id_from_context_key(context_key: LearningContextKey) -> int:
Expand Down
7 changes: 3 additions & 4 deletions openedx/core/djangoapps/content_libraries/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,10 @@ def assertDictContainsEntries(self, big_dict, subset_dict):
"""
Assert that the first dict contains at least all of the same entries as
the second dict.
Like python 2's assertDictContainsSubset, but with the arguments in the
correct order.
"""
assert big_dict.items() >= subset_dict.items()
for key, value in subset_dict.items():
assert key in big_dict, f"Missing key: {key}"
assert big_dict[key] == value, f"Value for key {key} does not match: expected {value}, got {big_dict[key]}"

def assertOrderEqual(self, libraries_list, expected_order):
"""
Expand Down
5 changes: 2 additions & 3 deletions openedx/core/djangoapps/cors_csrf/tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,14 +270,13 @@ def _assert_cookie_sent(self, response, is_set):
if is_set:
assert self.COOKIE_NAME in response.cookies
cookie_header = str(response.cookies[self.COOKIE_NAME])
# lint-amnesty, pylint: disable=bad-option-value, unicode-format-string

expected = 'Set-Cookie: {name}={value}; Domain={domain};'.format(
name=self.COOKIE_NAME,
value=self.COOKIE_VALUE,
domain=self.COOKIE_DOMAIN
)
assert expected in cookie_header
# added lower function because in python 3 the value of cookie_header has Secure and secure in python 2
assert 'Max-Age=31449600; Path=/; secure'.lower() in cookie_header.lower()
assert 'Max-Age=31449600; Path=/; Secure' in cookie_header
else:
assert self.COOKIE_NAME not in response.cookies
12 changes: 5 additions & 7 deletions openedx/core/djangoapps/crawlers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,14 @@ def is_crawler(cls, request):

# If there was no user agent detected or no crawler agents configured,
# then just return False.
if (not req_user_agent) or (not crawler_agents):
if not req_user_agent or not crawler_agents:
return False

# The crawler_agents list we pull from our model always has unicode objects, but the
# req_user_agent we get from HTTP headers ultimately comes to us via WSGI. That
# value is an ISO-8859-1 encoded byte string in Python 2.7 (and in the HTTP spec), but
# it will be a unicode str when we move to Python 3.x. This code should work under
# either version.
# Decode req_user_agent if it's bytes, so we can work with consistent string types.
if isinstance(req_user_agent, bytes):
crawler_agents = [crawler_agent.encode('iso-8859-1') for crawler_agent in crawler_agents]
req_user_agent = req_user_agent.decode('iso-8859-1')

crawler_agents = [crawler_agent.strip() for crawler_agent in crawler_agents]

# We perform prefix matching of the crawler agent here so that we don't
# have to worry about version bumps.
Expand Down
2 changes: 1 addition & 1 deletion openedx/core/lib/cache_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def decorator(*args, **kwargs): # pylint: disable=unused-argument,missing-docst

def zpickle(data):
"""Given any data structure, returns a zlib compressed pickled serialization."""
return zlib.compress(pickle.dumps(data, 4)) # Keep this constant as we upgrade from python 2 to 3.
return zlib.compress(pickle.dumps(data, 4))


def zunpickle(zdata):
Expand Down
8 changes: 3 additions & 5 deletions openedx/core/lib/grade_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,9 @@ def is_score_higher_or_equal(earned1, possible1, earned2, possible2, treat_undef
def round_away_from_zero(number, digits=0):
"""
Round numbers using the 'away from zero' strategy as opposed to the
'Banker's rounding strategy.' The strategy refers to how we round when
a number is half way between two numbers. eg. 0.5, 1.5, etc. In python 2
positive numbers in this category would be rounded up and negative numbers
would be rounded down. ie. away from zero. In python 3 numbers round
towards even. So 0.5 would round to 0 but 1.5 would round to 2.
'Banker's rounding strategy.' The strategy refers to how we round when
a number is half way between two numbers. eg. 0.5, 1.5, etc. In python 3
numbers round towards even. So 0.5 would round to 0 but 1.5 would round to 2.
See here for more on floating point rounding strategies:
https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules
Expand Down
36 changes: 7 additions & 29 deletions pavelib/paver_tests/test_timer.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,9 @@ def test_times(self):
messages = self.get_log_messages()
assert len(messages) == 1

# I'm not using assertDictContainsSubset because it is
# removed in python 3.2 (because the arguments were backwards)
# and it wasn't ever replaced by anything *headdesk*
assert 'duration' in messages[0]
assert 35.6 == messages[0]['duration']

assert 'started_at' in messages[0]
assert start.isoformat(' ') == messages[0]['started_at']

assert 'ended_at' in messages[0]
assert end.isoformat(' ') == messages[0]['ended_at']
assert 'duration' in messages[0] and messages[0]['duration'] == 35.6
assert 'started_at' in messages[0] and messages[0]['started_at'] == start.isoformat(' ')
assert 'ended_at' in messages[0] and messages[0]['ended_at'] == end.isoformat(' ')

@patch.object(timer, 'PAVER_TIMER_LOG', None)
def test_no_logs(self):
Expand All @@ -99,28 +91,18 @@ def test_arguments(self):
messages = self.get_log_messages(args=(1, 'foo'), kwargs=dict(bar='baz'))
assert len(messages) == 1

# I'm not using assertDictContainsSubset because it is
# removed in python 3.2 (because the arguments were backwards)
# and it wasn't ever replaced by anything *headdesk*
assert 'args' in messages[0]
assert [repr(1), repr('foo')] == messages[0]['args']
assert 'kwargs' in messages[0]
assert {'bar': repr('baz')} == messages[0]['kwargs']
assert 'args' in messages[0] and messages[0]['args'] == [repr(1), repr('foo')]
assert 'kwargs' in messages[0] and messages[0]['kwargs'] == {'bar': repr('baz')}

@patch.object(timer, 'PAVER_TIMER_LOG', '/tmp/some-log')
def test_task_name(self):
messages = self.get_log_messages()
assert len(messages) == 1

# I'm not using assertDictContainsSubset because it is
# removed in python 3.2 (because the arguments were backwards)
# and it wasn't ever replaced by anything *headdesk*
assert 'task' in messages[0]
assert 'pavelib.paver_tests.test_timer.identity' == messages[0]['task']
assert 'task' in messages[0] and messages[0]['task'] == 'pavelib.paver_tests.test_timer.identity'

@patch.object(timer, 'PAVER_TIMER_LOG', '/tmp/some-log')
def test_exceptions(self):

@timer.timed
def raises():
"""
Expand All @@ -131,11 +113,7 @@ def raises():
messages = self.get_log_messages(task=raises, raises=Exception)
assert len(messages) == 1

# I'm not using assertDictContainsSubset because it is
# removed in python 3.2 (because the arguments were backwards)
# and it wasn't ever replaced by anything *headdesk*
assert 'exception' in messages[0]
assert 'Exception: The Message!' == messages[0]['exception']
assert 'exception' in messages[0] and messages[0]['exception'] == 'Exception: The Message!'

@patch.object(timer, 'PAVER_TIMER_LOG', '/tmp/some-log-%Y-%m-%d-%H-%M-%S.log')
def test_date_formatting(self):
Expand Down
4 changes: 0 additions & 4 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@
# Ticket: https://github.com/openedx/edx-platform/issues/35334
algoliasearch<4.0.0

# Date: 2024-03-14
# Temporary to Support the python 3.11 Upgrade
# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35281
backports.zoneinfo;python_version<"3.9" # Newer versions have zoneinfo available in the standard library

# Date: 2020-02-26
# As it is not clarified what exact breaking changes will be introduced as per
Expand Down
2 changes: 1 addition & 1 deletion scripts/gha-shards-readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ You'd have to update the `unit-test-shards.json` file manually to fix this.
```
pytest --collect-only --ds=cms.envs.test cms/
```
For more details on how this check collects and compares the unit tests count please take a look at [verify unit tests count](../.github/workflows/verify-gha-unit-tests-count.yml)
For more details on how this check collects and compares the unit tests count please take a look at [verify unit tests count](../.github/workflows/unit-tests.yml)
4 changes: 2 additions & 2 deletions scripts/structures_pruning/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ To download the scripts, you can perform a partial clone of the edx-platform rep
Create Python Virtual Environment
---------------------------------

Create a Python virtual environment using Python 3.8:
Create a Python virtual environment using Python 3.11:

.. code-block:: bash
python3.8 -m venv ../venv
python3.11 -m venv ../venv
source ../venv/bin/activate
Install Pip Packages
Expand Down
4 changes: 2 additions & 2 deletions scripts/user_retirement/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ To download the scripts, you can perform a partial clone of the edx-platform rep
Create Python Virtual Environment
---------------------------------

Create a Python virtual environment using Python 3.8:
Create a Python virtual environment using Python 3.11:

.. code-block:: bash
python3.8 -m venv ../venv
python3.11 -m venv ../venv
source ../venv/bin/activate
Install Pip Packages
Expand Down
Loading

0 comments on commit ec2a698

Please sign in to comment.