diff --git a/CHANGELOG.md b/CHANGELOG.md index b478656..5fd6926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Changelog +### [2.1.1] - 2022-06-30 +#### Changed +- allow installation without the C++ extension if it fails to compile +- allow selection of implementation via the environment variable `RAPIDFUZZ_IMPLEMENTATION` + which can be set to "cpp" or "python" + ### [2.1.0] - 2022-06-29 #### Added - added pure python fallback for all implementations with the following exceptions: diff --git a/rapidfuzz/__init__.py b/rapidfuzz/__init__.py index d4689a3..419e2b0 100644 --- a/rapidfuzz/__init__.py +++ b/rapidfuzz/__init__.py @@ -1,8 +1,8 @@ """ rapid string matching library """ -__author__ = "Max Bachmann" -__license__ = "MIT" -__version__ = "2.1.0" +__author__: str = "Max Bachmann" +__license__: str = "MIT" +__version__: str = "2.1.0" from rapidfuzz import process, distance, fuzz, string_metric, utils diff --git a/rapidfuzz/distance/Hamming.py b/rapidfuzz/distance/Hamming.py index 325468e..25e2ba2 100644 --- a/rapidfuzz/distance/Hamming.py +++ b/rapidfuzz/distance/Hamming.py @@ -1,16 +1,10 @@ # SPDX-License-Identifier: MIT # Copyright (C) 2022 Max Bachmann -try: - from .Hamming_cpp import ( - distance, - similarity, - normalized_distance, - normalized_similarity, - ) -except ImportError: - from .Hamming_py import ( - distance, - similarity, - normalized_distance, - normalized_similarity, - ) + +from rapidfuzz.utils import _fallback_import + +_mod = "rapidfuzz.distance.Hamming" +distance = _fallback_import(_mod, "distance") +similarity = _fallback_import(_mod, "similarity") +normalized_distance = _fallback_import(_mod, "normalized_distance") +normalized_similarity = _fallback_import(_mod, "normalized_similarity") diff --git a/rapidfuzz/distance/Indel.py b/rapidfuzz/distance/Indel.py index 26973eb..28913d8 100644 --- a/rapidfuzz/distance/Indel.py +++ b/rapidfuzz/distance/Indel.py @@ -1,20 +1,12 @@ # SPDX-License-Identifier: MIT # Copyright (C) 2022 Max Bachmann -try: - from .Indel_cpp import ( - distance, - similarity, - normalized_distance, - normalized_similarity, - editops, - opcodes, - ) -except ImportError: - from .Indel_py import ( - distance, - similarity, - normalized_distance, - normalized_similarity, - editops, - opcodes, - ) + +from rapidfuzz.utils import _fallback_import + +_mod = "rapidfuzz.distance.Indel" +distance = _fallback_import(_mod, "distance") +similarity = _fallback_import(_mod, "similarity") +normalized_distance = _fallback_import(_mod, "normalized_distance") +normalized_similarity = _fallback_import(_mod, "normalized_similarity") +editops = _fallback_import(_mod, "editops") +opcodes = _fallback_import(_mod, "opcodes") diff --git a/rapidfuzz/distance/Jaro.py b/rapidfuzz/distance/Jaro.py index cb55542..c4eb21e 100644 --- a/rapidfuzz/distance/Jaro.py +++ b/rapidfuzz/distance/Jaro.py @@ -1 +1,6 @@ -from jarowinkler import jaro_similarity as similarity +try: + from jarowinkler import jaro_similarity as similarity +except ImportError: + + def similarity(s1, s2, *, processor=None, score_cutoff=None): + raise NotImplementedError diff --git a/rapidfuzz/distance/JaroWinkler.py b/rapidfuzz/distance/JaroWinkler.py index 25a3de1..0f76be0 100644 --- a/rapidfuzz/distance/JaroWinkler.py +++ b/rapidfuzz/distance/JaroWinkler.py @@ -1 +1,6 @@ -from jarowinkler import jarowinkler_similarity as similarity +try: + from jarowinkler import jarowinkler_similarity as similarity +except ImportError: + + def similarity(s1, s2, *, prefix_weight=0.1, processor=None, score_cutoff=None): + raise NotImplementedError diff --git a/rapidfuzz/distance/LCSseq.py b/rapidfuzz/distance/LCSseq.py index 0f8275f..2d73e7e 100644 --- a/rapidfuzz/distance/LCSseq.py +++ b/rapidfuzz/distance/LCSseq.py @@ -1,20 +1,12 @@ # SPDX-License-Identifier: MIT # Copyright (C) 2022 Max Bachmann -try: - from .LCSseq_cpp import ( - distance, - similarity, - normalized_distance, - normalized_similarity, - editops, - opcodes, - ) -except ImportError: - from .LCSseq_py import ( - distance, - similarity, - normalized_distance, - normalized_similarity, - editops, - opcodes, - ) + +from rapidfuzz.utils import _fallback_import + +_mod = "rapidfuzz.distance.LCSseq" +distance = _fallback_import(_mod, "distance") +similarity = _fallback_import(_mod, "similarity") +normalized_distance = _fallback_import(_mod, "normalized_distance") +normalized_similarity = _fallback_import(_mod, "normalized_similarity") +editops = _fallback_import(_mod, "editops") +opcodes = _fallback_import(_mod, "opcodes") diff --git a/rapidfuzz/distance/Levenshtein.py b/rapidfuzz/distance/Levenshtein.py index 5673153..6cb9101 100644 --- a/rapidfuzz/distance/Levenshtein.py +++ b/rapidfuzz/distance/Levenshtein.py @@ -1,20 +1,12 @@ # SPDX-License-Identifier: MIT # Copyright (C) 2022 Max Bachmann -try: - from .Levenshtein_cpp import ( - distance, - similarity, - normalized_distance, - normalized_similarity, - editops, - opcodes, - ) -except ImportError: - from .Levenshtein_py import ( - distance, - similarity, - normalized_distance, - normalized_similarity, - editops, - opcodes, - ) + +from rapidfuzz.utils import _fallback_import + +_mod = "rapidfuzz.distance.Levenshtein" +distance = _fallback_import(_mod, "distance") +similarity = _fallback_import(_mod, "similarity") +normalized_distance = _fallback_import(_mod, "normalized_distance") +normalized_similarity = _fallback_import(_mod, "normalized_similarity") +editops = _fallback_import(_mod, "editops") +opcodes = _fallback_import(_mod, "opcodes") diff --git a/rapidfuzz/distance/__init__.py b/rapidfuzz/distance/__init__.py index 997823a..87b4911 100644 --- a/rapidfuzz/distance/__init__.py +++ b/rapidfuzz/distance/__init__.py @@ -1,6 +1,13 @@ -try: - from ._initialize_cpp import Editop, Editops, Opcode, Opcodes, ScoreAlignment -except ImportError: - from ._initialize_py import Editop, Editops, Opcode, Opcodes, ScoreAlignment +# SPDX-License-Identifier: MIT +# Copyright (C) 2022 Max Bachmann + +from rapidfuzz.utils import _fallback_import + +_mod = "rapidfuzz.distance._initialize" +Editop = _fallback_import(_mod, "Editop") +Editops = _fallback_import(_mod, "Editops") +Opcode = _fallback_import(_mod, "Opcode") +Opcodes = _fallback_import(_mod, "Opcodes") +ScoreAlignment = _fallback_import(_mod, "ScoreAlignment") from . import Hamming, Indel, Jaro, JaroWinkler, Levenshtein, LCSseq diff --git a/rapidfuzz/fuzz.py b/rapidfuzz/fuzz.py index dd192c3..40fdd99 100644 --- a/rapidfuzz/fuzz.py +++ b/rapidfuzz/fuzz.py @@ -1,31 +1,17 @@ # SPDX-License-Identifier: MIT # Copyright (C) 2021 Max Bachmann -try: - from rapidfuzz.fuzz_cpp import ( - ratio, - partial_ratio, - partial_ratio_alignment, - token_sort_ratio, - partial_token_sort_ratio, - token_set_ratio, - partial_token_set_ratio, - token_ratio, - partial_token_ratio, - WRatio, - QRatio, - ) -except ImportError: - from rapidfuzz.fuzz_py import ( - ratio, - partial_ratio, - partial_ratio_alignment, - token_sort_ratio, - partial_token_sort_ratio, - token_set_ratio, - partial_token_set_ratio, - token_ratio, - partial_token_ratio, - WRatio, - QRatio, - ) +from rapidfuzz.utils import _fallback_import + +_mod = "rapidfuzz.fuzz" +ratio = _fallback_import(_mod, "ratio") +partial_ratio = _fallback_import(_mod, "partial_ratio") +partial_ratio_alignment = _fallback_import(_mod, "partial_ratio_alignment") +token_sort_ratio = _fallback_import(_mod, "token_sort_ratio") +token_set_ratio = _fallback_import(_mod, "token_set_ratio") +token_ratio = _fallback_import(_mod, "token_ratio") +partial_token_sort_ratio = _fallback_import(_mod, "partial_token_sort_ratio") +partial_token_set_ratio = _fallback_import(_mod, "partial_token_set_ratio") +partial_token_ratio = _fallback_import(_mod, "partial_token_ratio") +WRatio = _fallback_import(_mod, "WRatio") +QRatio = _fallback_import(_mod, "QRatio") diff --git a/rapidfuzz/process.py b/rapidfuzz/process.py index 76ce4ae..aaade78 100644 --- a/rapidfuzz/process.py +++ b/rapidfuzz/process.py @@ -1,17 +1,16 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2021 Max Bachmann +# Copyright (C) 2022 Max Bachmann + +from rapidfuzz.utils import _fallback_import + +_mod = "rapidfuzz.process" +extract = _fallback_import(_mod, "extract") +extractOne = _fallback_import(_mod, "extractOne") +extract_iter = _fallback_import(_mod, "extract_iter") try: - from rapidfuzz.process_cpp import extract, extractOne, extract_iter + cdist = _fallback_import("rapidfuzz.process_cdist", "cdist") except ImportError: - from rapidfuzz.process_py import extract, extractOne, extract_iter -try: - from rapidfuzz.process_cdist_cpp import cdist -except ImportError: - try: - from rapidfuzz.process_cdist_py import cdist - except ImportError: - - def cdist(*args, **kwargs): - raise NotImplementedError("implementation requires numpy to be installed") + def cdist(*args, **kwargs): + raise NotImplementedError("implementation requires numpy to be installed") diff --git a/rapidfuzz/string_metric.py b/rapidfuzz/string_metric.py index 825af17..30e3890 100644 --- a/rapidfuzz/string_metric.py +++ b/rapidfuzz/string_metric.py @@ -1,22 +1,13 @@ # SPDX-License-Identifier: MIT -# Copyright (C) 2021 Max Bachmann -try: - from rapidfuzz.string_metric_cpp import ( - levenshtein, - normalized_levenshtein, - levenshtein_editops, - hamming, - normalized_hamming, - jaro_similarity, - jaro_winkler_similarity, - ) -except ImportError: - from rapidfuzz.string_metric_py import ( - levenshtein, - normalized_levenshtein, - levenshtein_editops, - hamming, - normalized_hamming, - jaro_similarity, - jaro_winkler_similarity, - ) +# Copyright (C) 2022 Max Bachmann + +from rapidfuzz.utils import _fallback_import + +_mod = "rapidfuzz.string_metric" +levenshtein = _fallback_import(_mod, "levenshtein") +normalized_levenshtein = _fallback_import(_mod, "normalized_levenshtein") +levenshtein_editops = _fallback_import(_mod, "levenshtein_editops") +hamming = _fallback_import(_mod, "hamming") +normalized_hamming = _fallback_import(_mod, "normalized_hamming") +jaro_similarity = _fallback_import(_mod, "jaro_similarity") +jaro_winkler_similarity = _fallback_import(_mod, "jaro_winkler_similarity") diff --git a/rapidfuzz/utils.py b/rapidfuzz/utils.py index 9cd5502..62a02a5 100644 --- a/rapidfuzz/utils.py +++ b/rapidfuzz/utils.py @@ -1,7 +1,29 @@ # SPDX-License-Identifier: MIT # Copyright (C) 2022 Max Bachmann -try: - from rapidfuzz.utils_cpp import default_process -except ImportError: - from rapidfuzz.utils_py import default_process + +def _fallback_import(module: str, name: str): + import importlib + import os + + impl = os.environ.get("RAPIDFUZZ_IMPLEMENTATION") + + if impl == "cpp": + mod = importlib.import_module(module + "_cpp") + elif impl == "python": + mod = importlib.import_module(module + "_py") + else: + try: + mod = importlib.import_module(module + "_cpp") + except ModuleNotFoundError: + mod = importlib.import_module(module + "_py") + + func = getattr(mod, name) + if not func: + raise ImportError( + f"cannot import name '{name}' from '{mod.__name}' ({mod.__file__})" + ) + return func + + +default_process = _fallback_import("rapidfuzz.utils", "default_process") diff --git a/setup.py b/setup.py index 2fdd151..086135f 100644 --- a/setup.py +++ b/setup.py @@ -1,24 +1,28 @@ -from skbuild import setup -import rapidfuzz_capi -import numpy as np +import os + +def show_message(*lines): + print("=" * 74) + for line in lines: + print(line) + print("=" * 74) with open('README.md', 'rt', encoding="utf8") as f: readme = f.read() -setup( - name="rapidfuzz", - version="2.1.0", - install_requires=["jarowinkler >= 1.0.3, < 1.1.0"], - extras_require={'full': ['numpy']}, - url="https://github.com/maxbachmann/RapidFuzz", - author="Max Bachmann", - author_email="pypi@maxbachmann.de", - description="rapid fuzzy string matching", - long_description=readme, - long_description_content_type="text/markdown", +setup_args = { + "name": "rapidfuzz", + "version": "2.0.15", + "install_requires": ["jarowinkler >= 1.0.3, < 1.1.0"], + "extras_require": {'full': ['numpy']}, + "url": "https://github.com/maxbachmann/RapidFuzz", + "author": "Max Bachmann", + "author_email": "pypi@maxbachmann.de", + "description": "rapid fuzzy string matching", + "long_description": readme, + "long_description_content_type": "text/markdown", - license="MIT", - classifiers=[ + "license": "MIT", + "classifiers": [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", @@ -28,12 +32,52 @@ setup( "License :: OSI Approved :: MIT License" ], - packages=["rapidfuzz", "rapidfuzz/distance"], - package_data={ + "packages": ["rapidfuzz", "rapidfuzz/distance"], + "package_data": { "rapidfuzz": ["*.pyi", "py.typed"], "rapidfuzz/distance": ["*.pyi"] }, - python_requires=">=3.6", + "python_requires": ">=3.6" +} - cmake_args=[f'-DRF_CAPI_PATH:STRING={rapidfuzz_capi.get_include()}', f'-DNumPy_INCLUDE_DIR:STRING={np.get_include()}'] -) +def run_setup(with_binary): + if with_binary: + from skbuild import setup + import rapidfuzz_capi + import numpy as np + + setup( + **setup_args, + cmake_args=[ + f'-DRF_CAPI_PATH:STRING={rapidfuzz_capi.get_include()}', + f'-DNumPy_INCLUDE_DIR:STRING={np.get_include()}' + ] + ) + else: + from setuptools import setup + setup(**setup_args) + +# when packaging only build wheels which include the C extension +packaging = "1" in { + os.environ.get("CIBUILDWHEEL", "0"), + os.environ.get("CONDA_BUILD", "0"), + os.environ.get("RAPIDFUZZ_BUILD_EXTENSION", "0") +} +if packaging: + run_setup(True) +else: + try: + run_setup(True) + except: + show_message( + "WARNING: The C extension could not be compiled, speedups" + " are not enabled.", + "Failure information, if any, is above.", + "Retrying the build without the C extension now.", + ) + run_setup(False) + show_message( + "WARNING: The C extension could not be compiled, speedups" + " are not enabled.", + "Plain-Python build succeeded.", + )