cleanup references of python 2 & <3.11 (#35799)
* chore: cleanup of old python references
This commit is contained in:
2
.github/workflows/verify-dunder-init.yml
vendored
2
.github/workflows/verify-dunder-init.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: Verify Dunder __init__.py Files
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
@@ -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'),
|
||||
@@ -72,26 +64,18 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
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)
|
||||
|
||||
@@ -440,28 +440,19 @@ class DashboardTest(ModuleStoreTestCase, TestVerificationBase):
|
||||
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):
|
||||
|
||||
@@ -53,13 +53,11 @@ def cache_if_anonymous(*get_parameters):
|
||||
# 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:
|
||||
@@ -67,24 +65,20 @@ def cache_if_anonymous(*get_parameters):
|
||||
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)
|
||||
|
||||
@@ -86,15 +86,9 @@ class TestProgramListing(ProgramsApiConfigMixin, SharedModuleStoreTestCase):
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -86,11 +86,10 @@ class ContentLibrariesRestApiTest(APITransactionTestCase):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -270,14 +270,13 @@ class TestCsrfCrossDomainCookieMiddleware(TestCase):
|
||||
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
|
||||
|
||||
@@ -42,16 +42,14 @@ class CrawlersConfig(ConfigurationModel):
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -207,7 +207,7 @@ class CacheInvalidationManager:
|
||||
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -77,17 +77,9 @@ class TimedDecoratorTests(TestCase):
|
||||
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):
|
||||
@@ -99,28 +91,18 @@ class TimedDecoratorTests(TestCase):
|
||||
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():
|
||||
"""
|
||||
@@ -131,11 +113,7 @@ class TimedDecoratorTests(TestCase):
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -43,12 +43,11 @@ def _fail(kind, code, message):
|
||||
"""
|
||||
_log(kind, message)
|
||||
|
||||
# Try to get a traceback, if there is one. On Python 3.4 this raises an AttributeError
|
||||
# if there is no current exception, so we eat that here.
|
||||
try:
|
||||
_log(kind, traceback.format_exc())
|
||||
except AttributeError:
|
||||
pass
|
||||
# Log the traceback if an exception is currently being handled
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
if exc_type is not None:
|
||||
traceback_str = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
||||
_log(kind, ''.join(traceback_str))
|
||||
|
||||
sys.exit(code)
|
||||
|
||||
@@ -66,16 +65,12 @@ def _get_error_str_from_exception(exc):
|
||||
"""
|
||||
Return a string from an exception that may or may not have a .content (Slumber)
|
||||
"""
|
||||
exc_msg = text_type(exc)
|
||||
exc_msg = str(exc)
|
||||
|
||||
if hasattr(exc, 'content'):
|
||||
# Slumber inconveniently discards the decoded .text attribute from the Response object,
|
||||
# and instead gives us the raw encoded .content attribute, so we need to decode it first.
|
||||
# Python 2 needs the decode, Py3 does not have it.
|
||||
try:
|
||||
exc_msg += '\n' + str(exc.content).decode('utf-8')
|
||||
except AttributeError:
|
||||
exc_msg += '\n' + str(exc.content)
|
||||
# Attempt to decode `exc.content` if it's in bytes, otherwise just convert to str
|
||||
exc_content = exc.content.decode('utf-8') if isinstance(exc.content, bytes) else str(exc.content)
|
||||
exc_msg += '\n' + exc_content
|
||||
|
||||
return exc_msg
|
||||
|
||||
|
||||
@@ -306,13 +306,6 @@ class TestUpdateHash(unittest.TestCase):
|
||||
"""
|
||||
d1 = {k: 1 for k in "abcdefghijklmnopqrstuvwxyz"}
|
||||
d2 = {k: 1 for k in "bcdefghijklmnopqrstuvwxyza"}
|
||||
# TODO: remove the next lines once we are in python3.8
|
||||
# since python3.8 dict preserve the order of insertion
|
||||
# and therefore d2 and d1 keys are already in different order.
|
||||
for i in range(10000):
|
||||
d2[i] = 1
|
||||
for i in range(10000):
|
||||
del d2[i]
|
||||
|
||||
# Check that our dicts are equal, but with different key order.
|
||||
assert d1 == d2
|
||||
|
||||
Reference in New Issue
Block a user