diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..be006de --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 251eb0b..ddcbd25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,16 +6,16 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest"] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] include: - os: macos-latest - python-version: "3.13" + python-version: "3.x" # - os: windows-latest # TODO: Fix the Windows test that runs in an infinite loop # python-version: '3.13' runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true diff --git a/README.md b/README.md index 010cc8f..c55f87c 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,10 @@ You can also combine the flag options: ## Installation The current stable version of python-magic is available on PyPI and -can be installed by running `pip install python-magic`. +can be installed by running: +``` +pip install python-magic +``` Other sources: @@ -70,7 +73,7 @@ sudo apt-get install libmagic1 If python-magic fails to load the library it may be in a non-standard location, in which case you can set the environment variable `DYLD_LIBRARY_PATH` to point to it. ### SmartOS: -- Install libmagic for source https://github.com/threatstack/libmagic/ +- Install libmagic for source: https://github.com/file/file - Depending on your ./configure --prefix settings set your LD_LIBRARY_PATH to /lib ### Troubleshooting diff --git a/magic/__init__.py b/magic/__init__.py index 851b717..14d1896 100644 --- a/magic/__init__.py +++ b/magic/__init__.py @@ -18,11 +18,7 @@ import sys import os -import glob -import ctypes -import ctypes.util import threading -import logging from ctypes import c_char_p, c_int, c_size_t, c_void_p, byref, POINTER @@ -252,7 +248,7 @@ def from_descriptor(fd, mime=False): libmagic = loader.load_lib() -magic_t = ctypes.c_void_p +magic_t = c_void_p def errorcheck_null(result, func, args): diff --git a/magic/compat.py b/magic/compat.py index 6ab9400..32a7b93 100644 --- a/magic/compat.py +++ b/magic/compat.py @@ -8,7 +8,6 @@ from collections import namedtuple from ctypes import * -from ctypes.util import find_library from . import loader diff --git a/test/python_magic_test.py b/test/python_magic_test.py index 5076044..2639861 100755 --- a/test/python_magic_test.py +++ b/test/python_magic_test.py @@ -10,6 +10,12 @@ import pytest +try: + from concurrent.futures import ThreadPoolExecutor + HAS_CONCURRENT_FUTURES = True +except ImportError: # python 2.7 + HAS_CONCURRENT_FUTURES = False + # for output which reports a local time os.environ["TZ"] = "GMT" @@ -89,8 +95,8 @@ class TestFile: (NO_SOFT, ["data"]), ], b"test.snappy.parquet": [ - (COMMON_MIME, ["application/octet-stream"]), - (COMMON_PLAIN, ["Apache Parquet", "Par archive data"]), + (COMMON_MIME, ["application/octet-stream", "application/vnd.apache.parquet"]), + (COMMON_PLAIN, ["Apache Parquet", "Apache Parquet file", "Par archive data"]), (NO_SOFT, ["data"]), ], b"test.json": [ @@ -321,6 +327,25 @@ def test_symlink(self): self.assertRaises(IOError, m_follow.from_file, tmp_broken) + @unittest.skipIf(not HAS_CONCURRENT_FUTURES, "concurrent.futures not available in Python 2.7") + def test_thread_safety(self): + """Test that concurrent from_file calls don't crash (would SEGV without global lock)""" + filename = os.path.join(self.TESTDATA_DIR, "test.pdf") + + m = magic.Magic(mime=True) + + def check_file(_): + result = m.from_file(filename) + self.assertEqual(result, "application/pdf") + return result + + with ThreadPoolExecutor(100) as executor: + results = list(executor.map(check_file, range(100))) + + # All calls should complete successfully + self.assertEqual(len(results), 100) + self.assertTrue(all(r == "application/pdf" for r in results)) + if __name__ == "__main__": unittest.main() diff --git a/tox.ini b/tox.ini index 5c1648b..01cb7b2 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,8 @@ envlist = py311, py312, py313, + py314, + py314t, mypy [testenv]