Skip to content
Merged
Show file tree
Hide file tree
Changes from 54 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
93 changes: 93 additions & 0 deletions Doc/library/mimetypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,96 @@ than one MIME-type database; it provides an interface similar to the one of the
types, else to the list of non-standard types.

.. versionadded:: 3.2


.. 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 [-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.

The script converts file extensions to MIME types if ``--extension`` option
is specified, or vice versa if not.

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 aborts 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:: shell

# 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 http://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 -l filename.pict
type: image/pict encoding: None

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

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

# now look in the extended database again
$ python -m mimetypes -e -l 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 -e audio/aac audio/opus audio/future audio/x-wav
.aac
.opus
error: unknown type audio/future
73 changes: 30 additions & 43 deletions Lib/mimetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,49 +596,36 @@ def _default_mime_types():


def _main():
import getopt

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)
"""Run the mimetypes command line interface."""
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')
arguments = parser.parse_args()

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


if __name__ == '__main__':
Expand Down
74 changes: 40 additions & 34 deletions Lib/test/test_mimetypes.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import io
import mimetypes
from os import linesep
import pathlib
import sys
import unittest.mock

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 @@ -285,50 +287,54 @@ 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()
@classmethod
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.assertIn('usage: mimetypes.py', out)
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.assertIn('usage: mimetypes.py', err)

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"I don't know anything about 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.webp')
self.assertEqual(retcode, 1)
self.assertEqual(out, '')
self.assertEqual(err, f"I don't know anything about type foo.webp{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 @@ -814,6 +814,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 @@
Added *Command-Line Usage* section for :mod:`mimetypes`. Patch by Oleg Iarygin.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Command-line :mod:`mimetypes` 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.