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

gh-93096: Make mimetypes CLI tool public #93097

Merged
merged 68 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
494fba8
Make mimetypes CLI tool public
arhadthedev Mar 24, 2022
f7e288e
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev May 23, 2022
5fdd909
Add NEWS
arhadthedev May 23, 2022
1d778e2
No other module names the entry point _cli
arhadthedev May 23, 2022
d67704f
Adjust tests (capitalization of "Usage:" changed)
arhadthedev May 23, 2022
55df248
For CLI tests, make proper script invocations with proper checks
arhadthedev May 23, 2022
b065ed8
Address @AA-Turner's review
arhadthedev May 24, 2022
e7db03d
Move a constant check outside of a loop
arhadthedev May 24, 2022
6a1c96a
Allow .pic MIME type be image/x-pict
arhadthedev May 26, 2022
010354d
Follow PEP 8 more
arhadthedev May 26, 2022
3368be4
Change forgotten quotation marks
arhadthedev May 28, 2022
26bec36
Temporarily rollback everything but tests
arhadthedev May 28, 2022
f1d4364
Revert "Temporarily rollback everything but tests"
arhadthedev May 28, 2022
5e1de17
Add comments about assertIn()
arhadthedev May 28, 2022
e2ad463
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev May 28, 2022
48f1746
Normalize error codes too
arhadthedev May 28, 2022
11b067d
`from sys import exit` clashes with the exit builtin
arhadthedev May 28, 2022
5589f7a
Use f-strings for sys.exit()
arhadthedev May 28, 2022
3ade3a6
Use an extension unknown to macOS
arhadthedev May 28, 2022
895ac27
Fix an incorrect image/text to text/xul
arhadthedev May 28, 2022
4476b30
Fix stdout/stderr mistesting
arhadthedev May 28, 2022
96e127b
macOS, maybe Midi is what you don't override?
arhadthedev May 28, 2022
63b762e
One more attempt to fix macOS-specific tests
arhadthedev May 28, 2022
543d003
Maybe pict?
arhadthedev May 28, 2022
55c6165
Skip the strict case where mime.types is used
arhadthedev May 28, 2022
54f9889
Temporarily disable all test but the broken one
arhadthedev May 28, 2022
be06897
Temporarily remove doc building too
arhadthedev May 28, 2022
6af7668
temporary: Add printing of a platform name
arhadthedev May 28, 2022
037b68e
temporary: Another attempt
arhadthedev May 28, 2022
de4f377
Return to MIDI
arhadthedev May 28, 2022
a8e7718
Restore build scripts
arhadthedev May 28, 2022
df910bc
One more attempt
arhadthedev May 28, 2022
cf2a768
Fix a typo
arhadthedev May 29, 2022
c975d4c
Move a documentation-related news entry
arhadthedev Jun 2, 2022
bc1c707
Apply suggestions from the @AA-Turner's code review
arhadthedev Jun 10, 2022
1996d05
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev Jul 1, 2022
2aec434
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev Jul 21, 2022
9116a3d
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev Sep 25, 2022
64297c0
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev Oct 25, 2022
aeba820
Make CLI tests more strict
arhadthedev Oct 25, 2022
c94593f
Add newlines into assertEqual reference strings
arhadthedev Oct 25, 2022
8b20082
Clarify the NEWS entries
arhadthedev Oct 25, 2022
aa92570
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev Oct 30, 2022
ef15d7f
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev Nov 29, 2022
c942d89
Merge branch 'main' into normalize-mimetypes-cli
arhadthedev Jan 24, 2023
407413d
Slightly reword documentation
arhadthedev Jan 24, 2023
d58d5ba
Reword the news entry
arhadthedev Jan 24, 2023
0cfe67e
Add more examples
arhadthedev Jan 24, 2023
d2797f0
Clarify multi-input usage
arhadthedev Jan 24, 2023
fd590f5
Further rewording of the notes
arhadthedev Jan 24, 2023
1ef41dd
Clarify data source
arhadthedev Jan 24, 2023
2797fc4
Remove formatting-breaking "don't" from error messages
arhadthedev Jan 24, 2023
78c0c50
Fix a grammar mistake
arhadthedev Jan 24, 2023
1afb6d6
Make wording tighter
arhadthedev Jan 24, 2023
a22d630
Fix tests
arhadthedev Jan 24, 2023
b9d5309
Remove the command line input prefix
arhadthedev Jan 24, 2023
f6e2e16
Merge branch 'main' into normalize-mimetypes-cli
hugovk Mar 7, 2025
a9a43f7
Fix tests
hugovk Mar 7, 2025
85727bf
Fix test
hugovk Mar 7, 2025
f1535fd
Use console formatting for commands with output
hugovk Mar 7, 2025
8509c06
Docs: default case first, put condition first, avoid Latin, adjust wo…
hugovk Mar 8, 2025
9ad283c
Use long options in examples so no need to refer back to usage
hugovk Mar 8, 2025
a2c0d53
Follow argparse docs and use 'args'
hugovk Mar 8, 2025
34e0591
Add to What's New, combine NEWS files, update reference, use sentence…
hugovk Mar 8, 2025
80e1734
Hyphen
hugovk Mar 8, 2025
7cb62c5
Update error message
hugovk Mar 10, 2025
e23949a
Merge branch 'main' into normalize-mimetypes-cli
hugovk Mar 10, 2025
ce6999f
Merge remote-tracking branch 'upstream/main' into normalize-mimetypes…
hugovk Mar 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/library/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The following modules have a command-line interface.
* :mod:`!idlelib`
* :ref:`inspect <inspect-module-cli>`
* :ref:`json <json-commandline>`
* :mod:`mimetypes`
* :ref:`mimetypes <mimetypes-cli>`
* :mod:`pdb`
* :mod:`pickle`
* :ref:`pickletools <pickletools-cli>`
Expand Down
96 changes: 95 additions & 1 deletion Doc/library/mimetypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ An example usage of the module::

.. _mimetypes-objects:

MimeTypes Objects
MimeTypes objects
-----------------

The :class:`MimeTypes` class may be useful for applications which may want more
Expand Down Expand Up @@ -307,3 +307,97 @@ than one MIME-type database; it provides an interface similar to the one of the

When *strict* is ``True`` (the default), the mapping will be added to the
official MIME types, otherwise to the non-standard ones.


.. _mimetypes-cli:

Command-line usage
------------------

The :mod:`!mimetypes` module can be executed as a script from the command line.

.. code-block:: sh

python -m mimetypes [-h] [-e] [-l] type [type ...]

The following options are accepted:

.. program:: mimetypes

.. cmdoption:: -h
--help

Show the help message and exit.

.. cmdoption:: -e
--extension

Guess extension instead of type.

.. cmdoption:: -l
--lenient

Additionally search for some common, but non-standard types.

By default the script converts MIME types to file extensions.
However, if ``--extension`` is specified,
it converts file extensions to MIME types.

For each ``type`` entry, the script writes a line into the standard output
stream. If an unknown type occurs, it writes an error message into the
standard error stream and exits with the return code ``1``.


.. mimetypes-cli-example:

Command-line example
--------------------

Here are some examples of typical usage of the :mod:`!mimetypes` command-line
interface:

.. code-block:: console

$ # get a MIME type by a file name
$ python -m mimetypes filename.png
Copy link
Member

Choose a reason for hiding this comment

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

Some samples have $ prefix, some don't. I propose to remove all of them.

Grep for $ python, you won't find many examples.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, Doc/library in main has 14 prefixed submodule calls vs 18 non-prefixed.

$ git grep "$ python -m" | awk -F ':' '{print $1}' | sort -u | uniq | wc -l
14
$ git grep "$ python -m" | awk -F ':' '{print $1}' | sort -u | uniq
__main__.rst
asyncio.rst
importlib.metadata.rst
json.rst
pickletools.rst
shutil.rst
site.rst
sysconfig.rst
tarfile.rst
timeit.rst
tokenize.rst
uuid.rst
zipapp.rst
zipfile.rst
$ git grep "[^$] python -m" | awk -F ':' '{print $1}' | sort -u | uniq | wc -l
18
$ git grep "[^$] python -m" | awk -F ':' '{print $1}' | sort -u | uniq
ast.rst
doctest.rst
ensurepip.rst
http.server.rst
itertools.rst
json.rst
profile.rst
py_compile.rst
pydoc.rst
sqlite3.rst
timeit.rst
tokenize.rst
trace.rst
turtle.rst
unittest.rst
uuid.rst
webbrowser.rst
xmlrpc.server.rst

Copy link
Member Author

Choose a reason for hiding this comment

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

As for me, it looks confusing (like some config file). Any ideas on what to do next?

image

Copy link
Member Author

Choose a reason for hiding this comment

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

A side question: is there a point to use .. code-block:: shell-session instead, as in site?

Copy link
Member

Choose a reason for hiding this comment

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

I don't know :)
This is the first time I see this directive.

Copy link
Member

Choose a reason for hiding this comment

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

The general rule:

  • if only showing commands but no output: don't add prompts and use something like shell or shell-session or sh
  • if showing commands with output: add prompts and use something like console

For example, here's how Markdown does it, which isn't identical to RST but close enough.

sh

# get a MIME type by a file name
python -m mimetypes filename.png
type: image/png encoding: None
$ # get a MIME type by a file name
$ python -m mimetypes filename.png
type: image/png encoding: None

console

# get a MIME type by a file name
python -m mimetypes filename.png
type: image/png encoding: None
$ # get a MIME type by a file name
$ python -m mimetypes filename.png
type: image/png encoding: None

Copy link
Member

@hugovk hugovk Mar 7, 2025

Choose a reason for hiding this comment

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

Concretely, this:

.. code-block:: shell

   # get a MIME type by a file name
   python -m mimetypes filename.png
   type: image/png encoding: None

   # get a MIME type by a URL
   python -m mimetypes http://example.com/filename.txt
   type: text/plain encoding: None

gives:
image
And this:

.. code-block:: console

   $ # get a MIME type by a file name
   $ python -m mimetypes filename.png
   type: image/png encoding: None

   $ # get a MIME type by a URL
   $ python -m mimetypes http://example.com/filename.txt
   type: text/plain encoding: None

gives:
image

I've pushed an update.

type: image/png encoding: None

$ # get a MIME type by a URL
$ python -m mimetypes https://example.com/filename.txt
type: text/plain encoding: None

$ # get a complex MIME type
$ python -m mimetypes filename.tar.gz
type: application/x-tar encoding: gzip

$ # get a MIME type for a rare file extension
$ python -m mimetypes filename.pict
error: unknown extension of filename.pict

$ # now look in the extended database built into Python
$ python -m mimetypes --lenient filename.pict
type: image/pict encoding: None

$ # get a file extension by a MIME type
$ python -m mimetypes --extension text/javascript
.js

$ # get a file extension by a rare MIME type
$ python -m mimetypes --extension text/xul
error: unknown type text/xul

$ # now look in the extended database again
$ python -m mimetypes --extension --lenient text/xul
.xul

$ # try to feed an unknown file extension
$ python -m mimetypes filename.sh filename.nc filename.xxx filename.txt
type: application/x-sh encoding: None
type: application/x-netcdf encoding: None
error: unknown extension of filename.xxx

$ # try to feed an unknown MIME type
$ python -m mimetypes --extension audio/aac audio/opus audio/future audio/x-wav
.aac
.opus
error: unknown type audio/future
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,13 @@ json
mimetypes
---------

* Document the command-line for :mod:`mimetypes`.
It now exits with ``1`` on failure instead of ``0``
and ``2`` on incorrect command-line parameters instead of ``1``.
Also, errors are printed to stderr instead of stdout and their text is made
tighter.
(Contributed by Oleg Iarygin and Hugo van Kemenade in :gh:`93096`.)

* Add MS and :rfc:`8081` MIME types for fonts:

* Embedded OpenType: ``application/vnd.ms-fontobject``
Expand Down
74 changes: 31 additions & 43 deletions Lib/mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,50 +670,38 @@ def _default_mime_types():


def _main():
import getopt
"""Run the mimetypes command-line interface."""
import sys

USAGE = """\
Usage: mimetypes.py [options] type
Options:
--help / -h -- print this message and exit
--lenient / -l -- additionally search of some common, but non-standard
types.
--extension / -e -- guess extension instead of type
More than one type argument may be given.
"""

def usage(code, msg=''):
print(USAGE)
if msg: print(msg)
sys.exit(code)

try:
opts, args = getopt.getopt(sys.argv[1:], 'hle',
['help', 'lenient', 'extension'])
except getopt.error as msg:
usage(1, msg)

strict = 1
extension = 0
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(0)
elif opt in ('-l', '--lenient'):
strict = 0
elif opt in ('-e', '--extension'):
extension = 1
for gtype in args:
if extension:
guess = guess_extension(gtype, strict)
if not guess: print("I don't know anything about type", gtype)
else: print(guess)
else:
guess, encoding = guess_type(gtype, strict)
if not guess: print("I don't know anything about type", gtype)
else: print('type:', guess, 'encoding:', encoding)
from argparse import ArgumentParser

parser = ArgumentParser(description='map filename extensions to MIME types')
parser.add_argument(
'-e', '--extension',
action='store_true',
help='guess extension instead of type'
)
parser.add_argument(
'-l', '--lenient',
action='store_true',
help='additionally search for common but non-standard types'
)
parser.add_argument('type', nargs='+', help='a type to search')
args = parser.parse_args()

if args.extension:
for gtype in args.type:
guess = guess_extension(gtype, not args.lenient)
if guess:
print(guess)
else:
sys.exit(f"error: unknown type {gtype}")
else:
for gtype in args.type:
guess, encoding = guess_type(gtype, not args.lenient)
if guess:
print('type:', guess, 'encoding:', encoding)
else:
sys.exit(f"error: media type unknown for {gtype}")


if __name__ == '__main__':
Expand Down
73 changes: 39 additions & 34 deletions Lib/test/test_mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import os
import sys
import unittest.mock
from os import linesep

from test import support
from test.support import os_helper
from test.support.script_helper import run_python_until_end
from platform import win32_edition

try:
Expand Down Expand Up @@ -390,50 +392,53 @@ def test__all__(self):

class MimetypesCliTestCase(unittest.TestCase):

def mimetypes_cmd(self, *args, **kwargs):
support.patch(self, sys, "argv", [sys.executable, *args])
with support.captured_stdout() as output:
mimetypes._main()
return output.getvalue().strip()
def mimetypes_cmd(cls, *args, **kwargs):
result, _ = run_python_until_end('-m', 'mimetypes', *args)
return result.rc, result.out.decode(), result.err.decode()

def test_help_option(self):
support.patch(self, sys, "argv", [sys.executable, "-h"])
with support.captured_stdout() as output:
with self.assertRaises(SystemExit) as cm:
mimetypes._main()

self.assertIn("Usage: mimetypes.py", output.getvalue())
self.assertEqual(cm.exception.code, 0)
retcode, out, err = self.mimetypes_cmd('-h')
self.assertEqual(retcode, 0)
self.assertStartsWith(out, 'usage: ')
self.assertEqual(err, '')

def test_invalid_option(self):
support.patch(self, sys, "argv", [sys.executable, "--invalid"])
with support.captured_stdout() as output:
with self.assertRaises(SystemExit) as cm:
mimetypes._main()

self.assertIn("Usage: mimetypes.py", output.getvalue())
self.assertEqual(cm.exception.code, 1)
retcode, out, err = self.mimetypes_cmd('--invalid')
self.assertEqual(retcode, 2)
self.assertEqual(out, '')
self.assertStartsWith(err, 'usage: ')

def test_guess_extension(self):
eq = self.assertEqual

extension = self.mimetypes_cmd("-l", "-e", "image/jpg")
eq(extension, ".jpg")
retcode, out, err = self.mimetypes_cmd('-l', '-e', 'image/jpg')
self.assertEqual(retcode, 0)
self.assertEqual(out, f'.jpg{linesep}')
self.assertEqual(err, '')

extension = self.mimetypes_cmd("-e", "image/jpg")
eq(extension, "I don't know anything about type image/jpg")
retcode, out, err = self.mimetypes_cmd('-e', 'image/jpg')
self.assertEqual(retcode, 1)
self.assertEqual(out, '')
self.assertEqual(err, f'error: unknown type image/jpg{linesep}')

extension = self.mimetypes_cmd("-e", "image/jpeg")
eq(extension, ".jpg")
retcode, out, err = self.mimetypes_cmd('-e', 'image/jpeg')
self.assertEqual(retcode, 0)
self.assertEqual(out, f'.jpg{linesep}')
self.assertEqual(err, '')

def test_guess_type(self):
eq = self.assertEqual

type_info = self.mimetypes_cmd("-l", "foo.pic")
eq(type_info, "type: image/pict encoding: None")

type_info = self.mimetypes_cmd("foo.pic")
eq(type_info, "I don't know anything about type foo.pic")
retcode, out, err = self.mimetypes_cmd('-l', 'foo.webp')
self.assertEqual(retcode, 0)
self.assertEqual(out, f'type: image/webp encoding: None{linesep}')
self.assertEqual(err, '')

@unittest.skipIf(
sys.platform == 'darwin',
'macOS lists common_types in mime.types thus making them always known'
)
def test_guess_type_conflicting_with_mimetypes(self):
retcode, out, err = self.mimetypes_cmd('foo.pic')
self.assertEqual(retcode, 1)
self.assertEqual(out, '')
self.assertEqual(err, f'error: media type unknown for foo.pic{linesep}')

if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ Oleg Höfling
Robert Hölzl
Stefan Hölzl
Catalin Iacob
Oleg Iarygin
Mihai Ibanescu
Ali Ikinci
Aaron Iles
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Document the command-line for :mod:`mimetypes`.
It now exits with ``1`` on failure instead of ``0``
and ``2`` on incorrect command-line parameters instead of ``1``.
Also, errors are printed to stderr instead of stdout and their text is made
tighter. Patch by Oleg Iarygin and Hugo van Kemenade.
Loading