From d383bdb9ea854874976e9237229a52347a3d4e11 Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Sat, 6 May 2023 10:34:00 +1200 Subject: [PATCH 1/2] Update various docstrings. --- colour/utilities/tests/test_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colour/utilities/tests/test_common.py b/colour/utilities/tests/test_common.py index 1f84ca081..1fcdf4456 100644 --- a/colour/utilities/tests/test_common.py +++ b/colour/utilities/tests/test_common.py @@ -505,7 +505,7 @@ class TestSlugify(unittest.TestCase): """ def test_slugify(self): - """Test :func:`colour.utilities.common.optional` definition.""" + """Test :func:`colour.utilities.common.slugify` definition.""" self.assertEqual( slugify( From 4d3dc25ef09fa3e0738cd667c6e46d0f52cf53bb Mon Sep 17 00:00:00 2001 From: Thomas Mansencal Date: Sat, 6 May 2023 10:38:47 +1200 Subject: [PATCH 2/2] Add "colour.utilities.int_digest" definition. --- colour/utilities/__init__.py | 2 + colour/utilities/common.py | 80 +++++++++++++++++++++++++++ colour/utilities/tests/test_common.py | 25 +++++++++ colour/utilities/verbose.py | 15 ++--- docs/colour.utilities.rst | 2 + pyproject.toml | 3 +- 6 files changed, 119 insertions(+), 8 deletions(-) diff --git a/colour/utilities/__init__.py b/colour/utilities/__init__.py index a516926d3..1a4f6dca0 100644 --- a/colour/utilities/__init__.py +++ b/colour/utilities/__init__.py @@ -50,6 +50,7 @@ validate_method, optional, slugify, + int_digest, ) from .verbose import ( ColourWarning, @@ -167,6 +168,7 @@ "validate_method", "optional", "slugify", + "int_digest", ] __all__ += [ "ColourWarning", diff --git a/colour/utilities/common.py b/colour/utilities/common.py index 8e3c2eb55..07a2d7e5c 100644 --- a/colour/utilities/common.py +++ b/colour/utilities/common.py @@ -73,6 +73,7 @@ "is_pandas_installed", "is_tqdm_installed", "is_trimesh_installed", + "is_xxhash_installed", "required", "is_iterable", "is_string", @@ -86,6 +87,7 @@ "validate_method", "optional", "slugify", + "int_digest", ] @@ -894,6 +896,42 @@ def is_trimesh_installed(raise_exception: bool = False) -> bool: return False +def is_xxhash_installed(raise_exception: bool = False) -> bool: + """ + Return whether *xxhash* is installed and available. + + Parameters + ---------- + raise_exception + Whether to raise an exception if *xxhash* is unavailable. + + Returns + ------- + :class:`bool` + Whether *xxhash* is installed. + + Raises + ------ + :class:`ImportError` + If *xxhash* is not installed. + """ + + try: # pragma: no cover + # pylint: disable=W0611 + import xxhash # noqa: F401 + + return True + except ImportError as error: # pragma: no cover + if raise_exception: + raise ImportError( + '"xxhash" related API features are not available: ' + f'"{error}".\nSee the installation guide for more information: ' + "https://www.colour-science.org/installation-guide/" + ) from error + + return False + + _REQUIREMENTS_TO_CALLABLE: CanonicalMapping = CanonicalMapping( { "ctlrender": is_ctlrender_installed, @@ -905,6 +943,7 @@ def is_trimesh_installed(raise_exception: bool = False) -> bool: "Pandas": is_pandas_installed, "tqdm": is_tqdm_installed, "trimesh": is_trimesh_installed, + "xxhash": is_xxhash_installed, } ) """ @@ -923,6 +962,7 @@ def required( "Pandas", "tqdm", "trimesh", + "xxhash", ] ) -> Callable: """ @@ -1427,3 +1467,43 @@ def slugify(object_: Any, allow_unicode: bool = False) -> str: value = re.sub(r"[^\w\s-]", "", value.lower()) return re.sub(r"[-\s]+", "-", value).strip("-_") + + +if is_xxhash_installed(): + import xxhash + from colour.utilities.documentation import is_documentation_building + + int_digest = xxhash.xxh3_64_intdigest # pyright: ignore + + if is_documentation_building(): # pragma: no cover + import array + + def int_digest( + args: str # noqa: ARG001 + | bytes + | bytearray + | memoryview + | array.ArrayType[int], + seed: int = 0, # noqa: ARG001 + ) -> int: + """ + Generate an integer digest for given argument using *xxhash* if + available or falling back to :func:`hash` if not. + + Parameters + ---------- + args + Argument to generate the int digest of. + seed + Seed used to alter result predictably. + + Returns + ------- + :class:`int` + Integer digest. + """ + + return -1 + +else: + int_digest = hash # pyright: ignore diff --git a/colour/utilities/tests/test_common.py b/colour/utilities/tests/test_common.py index 1fcdf4456..871fdd537 100644 --- a/colour/utilities/tests/test_common.py +++ b/colour/utilities/tests/test_common.py @@ -4,6 +4,7 @@ from __future__ import annotations import numpy as np +import platform import unittest import unicodedata from functools import partial @@ -26,6 +27,7 @@ validate_method, optional, slugify, + int_digest, ) __author__ = "Colour Developers" @@ -536,5 +538,28 @@ def test_slugify(self): self.assertEqual(slugify(123), "123") +class TestIntDigest(unittest.TestCase): + """ + Define :func:`colour.utilities.common.int_digest` definition unit tests + methods. + """ + + def test_int_digest(self): + """Test :func:`colour.utilities.common.int_digest` definition.""" + + self.assertEqual(int_digest("Foo"), 7467386374397815550) + + if platform.system() in ("Windows", "Microsoft"): # pragma: no cover + self.assertEqual( + int_digest(np.array([1, 2, 3]).tobytes()), 7764052002911021640 + ) + else: + self.assertEqual( + int_digest(np.array([1, 2, 3]).tobytes()), 8964613590703056768 + ) + + self.assertEqual(int_digest(repr((1, 2, 3))), 5069958125469218295) + + if __name__ == "__main__": unittest.main() diff --git a/colour/utilities/verbose.py b/colour/utilities/verbose.py index fcd77244c..94a8658d5 100644 --- a/colour/utilities/verbose.py +++ b/colour/utilities/verbose.py @@ -13,7 +13,7 @@ import traceback import warnings from collections import defaultdict -from contextlib import contextmanager +from contextlib import contextmanager, suppress from itertools import chain from textwrap import TextWrapper from warnings import filterwarnings, formatwarning, warn @@ -655,18 +655,19 @@ def describe_environment( "tqdm", "trimesh", ]: - try: + with suppress(ImportError): namespace = __import__(package) environment["Runtime"][package] = namespace.__version__ - except ImportError: - continue # OpenImageIO - try: # pragma: no cover + with suppress(ImportError): # pragma: no cover namespace = __import__("OpenImageIO") environment["Runtime"]["OpenImageIO"] = namespace.VERSION_STRING - except ImportError: # pragma: no cover - pass + + # xxhash + with suppress(ImportError): # pragma: no cover + namespace = __import__("xxhash") + environment["Runtime"]["xxhash"] = namespace.version.VERSION environment["Runtime"].update(ANCILLARY_RUNTIME_PACKAGES) diff --git a/docs/colour.utilities.rst b/docs/colour.utilities.rst index 36821b3eb..8cbf3fa81 100644 --- a/docs/colour.utilities.rst +++ b/docs/colour.utilities.rst @@ -68,6 +68,7 @@ Common is_pandas_installed is_tqdm_installed is_trimesh_installed + is_xxhash_installed required is_iterable is_string @@ -81,6 +82,7 @@ Common validate_method optional slugify + int_digest Array ----- diff --git a/pyproject.toml b/pyproject.toml index ee17c4ad6..33581b017 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,7 @@ pandas = { version = ">= 1.3, < 2", optional = true } pygraphviz = { version = ">= 1, < 2", optional = true } tqdm = { version = ">= 4, < 5", optional = true } trimesh = { version = ">= 3, < 4", optional = true } +xxhash = { version = ">= 3.2, < 4", optional = true } biblib-simple = { version = "*", optional = true } # Development dependency. black = { version = "*", optional = true } # Development dependency. @@ -124,7 +125,7 @@ development = [ ] graphviz = [ "pygraphviz" ] meshing = [ "trimesh" ] -optional = [ "networkx", "pandas", "scikit-learn", "tqdm" ] +optional = [ "networkx", "pandas", "scikit-learn", "tqdm", "xxhash" ] plotting = [ "matplotlib" ] read-the-docs = [ "matplotlib",