diff --git a/.config/constraints.txt b/.config/constraints.txt index 81f01aee9..15c782b21 100644 --- a/.config/constraints.txt +++ b/.config/constraints.txt @@ -17,7 +17,6 @@ docutils==0.21.2 exceptiongroup==1.2.2 ; python_full_version < '3.11' execnet==2.1.1 executing==2.2.0 -filemagic==1.6 flaky==3.8.1 furo==2024.8.6 gssapi==1.9.0 ; sys_platform != 'win32' diff --git a/docs/conf.py b/docs/conf.py index ebe6d53c0..fe6684df9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,7 +56,6 @@ ("py:class", "jira.resources.AnyLike"), # Dummy subclass for type checking ("py:meth", "__recoverable"), # ResilientSession, not autogenerated # From other packages - ("py:mod", "filemagic"), ("py:mod", "ipython"), ("py:mod", "pip"), ("py:class", "_io.BufferedReader"), diff --git a/docs/installation.rst b/docs/installation.rst index 3b0cf048c..e433f351f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -38,6 +38,5 @@ Dependencies - :py:mod:`requests-oauthlib` - Used to implement OAuth. The latest version as of this writing is 1.3.0. - :py:mod:`requests-kerberos` - Used to implement Kerberos. - :py:mod:`ipython` - The `IPython enhanced Python interpreter `_ provides the fancy chrome used by :ref:`jirashell-label`. -- :py:mod:`filemagic` - This library handles content-type autodetection for things like image uploads. This will only work on a system that provides libmagic; Mac and Unix will almost always have it preinstalled, but Windows users will have to use Cygwin or compile it natively. If your system doesn't have libmagic, you'll have to manually specify the ``contentType`` parameter on methods that take an image object, such as project and user avatar creation. Installing through :py:mod:`pip` takes care of these dependencies for you. diff --git a/jira/client.py b/jira/client.py index b23aff2de..4f1a49e6d 100644 --- a/jira/client.py +++ b/jira/client.py @@ -14,11 +14,9 @@ import hashlib import json import logging as _logging -import mimetypes import os import re import sys -import tempfile import time import urllib import warnings @@ -606,8 +604,6 @@ def __init__( if len(context_path) > 0: self._options["context_path"] = context_path - self._try_magic() - assert isinstance(self._options["headers"], dict) # for mypy benefit # Create Session object and update with config options first @@ -3336,9 +3332,7 @@ def create_temp_project_avatar( The avatar created is temporary and must be confirmed before it can be used. - Avatar images are specified by a filename, size, and file object. By default, the client will attempt to autodetect the picture's content type - this mechanism relies on libmagic and will not work out of the box on Windows systems - (see `Their Documentation `_ for details on how to install support). + Avatar images are specified by a filename, size, and file object. By default, the client will attempt to autodetect the picture's content type. The ``contentType`` argument can be used to explicitly set the value (note that Jira will reject any type other than the well-known ones for images, e.g. ``image/jpg``, ``image/png``, etc.) @@ -4051,9 +4045,7 @@ def create_temp_user_avatar( The avatar created is temporary and must be confirmed before it can be used. - Avatar images are specified by a filename, size, and file object. By default, the client will attempt to autodetect the picture's content type: - this mechanism relies on ``libmagic`` and will not work out of the box on Windows systems - (see `Their Documentation `_ for details on how to install support). + Avatar images are specified by a filename, size, and file object. By default, the client will attempt to autodetect the picture's content type. The ``contentType`` argument can be used to explicitly set the value (note that Jira will reject any type other than the well-known ones for images, e.g. ``image/jpg``, ``image/png``, etc.) @@ -4631,29 +4623,8 @@ def _find_for_resource( raise JIRAError("Unable to find resource %s(%s)", resource_cls, str(ids)) return resource - def _try_magic(self): - try: - import weakref - - import magic - except ImportError: - self._magic = None - else: - try: - _magic = magic.Magic(flags=magic.MAGIC_MIME_TYPE) - - def cleanup(x): - _magic.close() - - self._magic_weakref = weakref.ref(self, cleanup) - self._magic = _magic - except TypeError: - self._magic = None - except AttributeError: - self._magic = None - def _get_mime_type(self, buff: bytes) -> str | None: - """Get the MIME type for a given stream of bytes. + """Get the MIME type for a given stream of bytes of an avatar. Args: buff (bytes): Stream of bytes @@ -4661,19 +4632,23 @@ def _get_mime_type(self, buff: bytes) -> str | None: Returns: Optional[str]: the MIME type """ - if self._magic is not None: - return self._magic.id_buffer(buff) - try: - with tempfile.TemporaryFile() as f: - f.write(buff) - return mimetypes.guess_type(f.name)[0] - return mimetypes.guess_type(f.name)[0] - except (OSError, TypeError): - self.log.warning( - "Couldn't detect content type of avatar image" - ". Specify the 'contentType' parameter explicitly." - ) - return None + # We assume the image is one of supported formats + # https://docs.atlassian.com/software/jira/docs/api/REST/9.14.0/#api/2/project-storeTemporaryAvatar + if buff[:3] == b"\xff\xd8\xff": + return "image/jpeg" + if buff[:8] == b"\x89PNG\r\n\x1a\n": + return "image/png" + if buff[:6] in (b"GIF87a", b"GIF89a"): + return "image/gif" + if buff[:2] == b"BM": + return "image/bmp" + if buff[:2] == b"\0\0": + return "image/vnd.wap.wbmp" + self.log.warning( + "Couldn't detect content type of avatar image" + ". Specify the 'contentType' parameter explicitly." + ) + return None def rename_user(self, old_user: str, new_user: str): """Rename a Jira user. diff --git a/pyproject.toml b/pyproject.toml index 8e550d29b..cd6312944 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ docs = [ "furo" ] opt = [ - "filemagic>=1.6", "PyJWT", "requests_jwt", "requests_kerberos"