Skip to content

gh-92455: Respect case-sensitive mimetype suffixes#148782

Merged
serhiy-storchaka merged 20 commits into
python:mainfrom
yuanx749:fix-mimetypes-case-sensitive-add-type
Jun 15, 2026
Merged

gh-92455: Respect case-sensitive mimetype suffixes#148782
serhiy-storchaka merged 20 commits into
python:mainfrom
yuanx749:fix-mimetypes-case-sensitive-add-type

Conversation

@yuanx749

Copy link
Copy Markdown
Contributor

Fixes gh-92455.

As described in the issue, the current implementation cannot find uppercase
extensions added via mimetypes.add_type(). This appears to be a regression from gh-30229.

This restores the lookup order documented for mimetypes.guess_type():
type suffixes are tried case-sensitively first, then case-insensitively.

@ifrh ifrh left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I cannot approve the changes formaly, but they look good for me.

Comment thread Lib/test/test_mimetypes.py Outdated
eq(self.db.guess_type("scheme:foobar.tar.z"), (None, None))

def test_added_types_case_sensitive_preferred(self):
self.db.add_type("text/x-r-script", ".R")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"text/x-r-script" could be added to the database in future. It is better to use something less likely to be added.

Please add also tests for strict=False.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review. I changed the test type and added tests for strict=False.

@serhiy-storchaka

Copy link
Copy Markdown
Member

#30229 changed also the code related to suffix_map. I wonder if it needs correction too:

        while True:
            if ext in self.suffix_map:
                base, ext = posixpath.splitext(base + self.suffix_map[ext])
                continue
            ext_lower = ext.lower():
            if ext_lower in self.suffix_map:
                base, ext = posixpath.splitext(base + self.suffix_map[ext_lower])
                continue
            break

@kumaraditya303, what are your thoughts?

@kumaraditya303

Copy link
Copy Markdown
Contributor

what are your thoughts?

I think we should do the same for suffix_map i.e. first try case sensitively then case-insensitively.

@yuanx749

yuanx749 commented Jun 6, 2026

Copy link
Copy Markdown
Contributor Author

Would you prefer handling suffix_map in this PR, or keeping this focused on types_map?

@serhiy-storchaka

Copy link
Copy Markdown
Member

It is better to handle all in one PR. Also please update the documentation.

@yuanx749

yuanx749 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Added a failing test, then updated suffix_map to try the original suffix before the lower case suffix. I also updated the documentation. The existing strict=False lookup order is unchanged.

@read-the-docs-community

read-the-docs-community Bot commented Jun 8, 2026

Copy link
Copy Markdown

@serhiy-storchaka serhiy-storchaka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There are open questions: Should we try case-sensitive in non-strict mapping before case-insensitive in strict mapping? What to do with asymmetry in registering upper-case and lower-case extensions? I do not see clear answers on them, so we can leave them to other issues (when we get clear answers).

I think that the documentation for add_type() should be updated. Registered lower-case extensions will match any case.

@yuanx749

yuanx749 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

There are open questions: Should we try case-sensitive in non-strict mapping before case-insensitive in strict mapping? What to do with asymmetry in registering upper-case and lower-case extensions? I do not see clear answers on them, so we can leave them to other issues (when we get clear answers).

I think that the documentation for add_type() should be updated. Registered lower-case extensions will match any case.

The original lookup behavior is now preserved. I have also updated the add_type() and MimeTypes.add_type() docstrings and the corresponding documentation.

@serhiy-storchaka serhiy-storchaka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. 👍

@serhiy-storchaka serhiy-storchaka enabled auto-merge (squash) June 15, 2026 14:42
@serhiy-storchaka serhiy-storchaka merged commit 46107ad into python:main Jun 15, 2026
93 of 95 checks passed
@yuanx749 yuanx749 deleted the fix-mimetypes-case-sensitive-add-type branch June 15, 2026 15:15
@bedevere-bot

Copy link
Copy Markdown

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot aarch64 Android 3.x (tier-3) has failed when building commit 46107ad.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/#/builders/1594/builds/5095) and take a look at the build logs.
  4. Check if the failure is related to this commit (46107ad) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/#/builders/1594/builds/5095

Failed tests:

  • test_urllib2
  • test_urllibnet

Failed subtests:

  • test_ftp_error - test.test_urllib2.HandlerTests.test_ftp_error
  • test_getcode - test.test_urllibnet.urlopenNetworkTests.test_getcode

Summary of the results of the build (if available):

==

Click to see traceback logs
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 1531, in ftp_open
    host = socket.gethostbyname(host)
socket.gaierror: [Errno 7] No address associated with hostname
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/test/test_urllib2.py", line 815, in test_ftp_error
    urlopen("ftp://www.pythontest.net/")
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 489, in open
    response = self._open(req, data)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 506, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
                              '_open', req)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 466, in _call_chain
    result = func(*args)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 1533, in ftp_open
    raise URLError(msg)
urllib.error.URLError: <urlopen error [Errno 7] No address associated with hostname>
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/test/test_urllib2.py", line 817, in test_ftp_error
    self.assertEqual(raised.reason,
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
                     f"ftp error: {exception.args[0]}")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: gaierror(7, 'No address associated with hostname') != 'ftp error: 500 OOPS: cannot change directory:/nonexistent'
 
----------------------------------------------------------------------
Ran 81 tests in 0.074s
 
FAILED (failures=1, skipped=3)


Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 1531, in ftp_open
    host = socket.gethostbyname(host)
socket.gaierror: [Errno 7] No address associated with hostname
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/test/test_urllib2.py", line 815, in test_ftp_error
    urlopen("ftp://www.pythontest.net/")
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 489, in open
    response = self._open(req, data)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 506, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
                              '_open', req)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 466, in _call_chain
    result = func(*args)
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/urllib/request.py", line 1533, in ftp_open
    raise URLError(msg)
urllib.error.URLError: <urlopen error [Errno 7] No address associated with hostname>
 
During handling of the above exception, another exception occurred:
 
Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/test/test_urllib2.py", line 817, in test_ftp_error
    self.assertEqual(raised.reason,
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
                     f"ftp error: {exception.args[0]}")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: gaierror(7, 'No address associated with hostname') != 'ftp error: 500 OOPS: cannot change directory:/nonexistent'
 
----------------------------------------------------------------------
Ran 81 tests in 0.050s
 
FAILED (failures=1, skipped=3)


Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/test/test_urllibnet.py", line 107, in test_getcode
    self.assertEqual(e.exception.code, 404)
                     ^^^^^^^^^^^^^^^^
AttributeError: 'URLError' object has no attribute 'code'
 
----------------------------------------------------------------------
Ran 12 tests in 0.034s
 
FAILED (errors=1, skipped=10)


Traceback (most recent call last):
  File "/data/user/0/org.python.testbed/files/python/lib/python3.16/test/test_urllibnet.py", line 107, in test_getcode
    self.assertEqual(e.exception.code, 404)
                     ^^^^^^^^^^^^^^^^
AttributeError: 'URLError' object has no attribute 'code'
 
----------------------------------------------------------------------
Ran 12 tests in 0.031s
 
FAILED (errors=1, skipped=10)

philthompson10 pushed a commit to philthompson10/cpython that referenced this pull request Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants