Repository: adamchainz/django-mysql

Issue:
Deprecate mathematical db functions
Following discussion in #482 , it's probably best to deprecate the mathematical functions, as the will/could be replicated in django core with multi-backend compatbility. Probably should use this as a reason to go implement some there myself.


Discussion:

Resolving patch:
diff --git a/src/django_mysql/models/functions.py b/src/django_mysql/models/functions.py
--- a/src/django_mysql/models/functions.py
+++ b/src/django_mysql/models/functions.py
@@ -2,6 +2,7 @@
 
 import datetime as dt
 import json
+import warnings
 from typing import Any, Union
 
 from django.db import DEFAULT_DB_ALIAS, connections
@@ -58,6 +59,17 @@ class Sign(SingleArgFunc):
     function = "SIGN"
     output_field_class = IntegerField
 
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        warnings.warn(
+            (
+                "This function is deprecated. Use "
+                + "django.db.models.functions.Sign instead."
+            ),
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        super().__init__(*args, **kwargs)
+
 
 # String Functions
 
@@ -159,11 +171,27 @@ class MD5(SingleArgFunc):
     function = "MD5"
     output_field_class = CharField
 
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        warnings.warn(
+            "This function is deprecated. Use django.db.models.functions.MD5 instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        super().__init__(*args, **kwargs)
+
 
 class SHA1(SingleArgFunc):
     function = "SHA1"
     output_field_class = CharField
 
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
+        warnings.warn(
+            "This function is deprecated. Use django.db.models.functions.SHA1 instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        super().__init__(*args, **kwargs)
+
 
 class SHA2(Func):
     function = "SHA2"
@@ -176,6 +204,14 @@ def __init__(self, expression: ExpressionArgument, hash_len: int = 512) -> None:
                     ",".join(str(x) for x in self.hash_lens)
                 )
             )
+        warnings.warn(
+            (
+                "This function is deprecated. Use "
+                + "django.db.models.functions.SHA{hash_len} instead."
+            ),
+            DeprecationWarning,
+            stacklevel=2,
+        )
         super().__init__(expression, Value(hash_len), output_field=TextField())
 
 


Test patch:
diff --git a/tests/testapp/test_functions.py b/tests/testapp/test_functions.py
--- a/tests/testapp/test_functions.py
+++ b/tests/testapp/test_functions.py
@@ -135,9 +135,18 @@ def test_crc32(self):
 
     def test_sign(self):
         Alphabet.objects.create(a=123, b=0, c=-999)
-        ab = Alphabet.objects.annotate(
-            asign=Sign("a"), bsign=Sign("b"), csign=Sign("c")
-        ).first()
+
+        with self.assertWarnsMessage(
+            DeprecationWarning, "This function is deprecated."
+        ):
+            kwargs = {
+                "asign": Sign("a"),
+                "bsign": Sign("b"),
+                "csign": Sign("c"),
+            }
+
+        ab = Alphabet.objects.annotate(**kwargs).first()
+
         assert ab.asign == 1
         assert ab.bsign == 0
         assert ab.csign == -1
@@ -287,14 +296,26 @@ def test_md5_string(self):
         string = "A string"
         Alphabet.objects.create(d=string)
         pymd5 = hashlib.md5(string.encode("ascii")).hexdigest()
-        ab = Alphabet.objects.annotate(md5=MD5("d")).first()
+
+        with self.assertWarnsMessage(
+            DeprecationWarning, "This function is deprecated."
+        ):
+            md5 = MD5("d")
+        ab = Alphabet.objects.annotate(md5=md5).first()
+
         assert ab.md5 == pymd5
 
     def test_sha1_string(self):
         string = "A string"
         Alphabet.objects.create(d=string)
         pysha1 = hashlib.sha1(string.encode("ascii")).hexdigest()
-        ab = Alphabet.objects.annotate(sha=SHA1("d")).first()
+
+        with self.assertWarnsMessage(
+            DeprecationWarning, "This function is deprecated."
+        ):
+            sha1 = SHA1("d")
+        ab = Alphabet.objects.annotate(sha=sha1).first()
+
         assert ab.sha == pysha1
 
     def test_sha2_string(self):
@@ -304,14 +325,26 @@ def test_sha2_string(self):
         for hash_len in (224, 256, 384, 512):
             sha_func = getattr(hashlib, f"sha{hash_len}")
             pysha = sha_func(string.encode("ascii")).hexdigest()
-            ab = Alphabet.objects.annotate(sha=SHA2("d", hash_len)).first()
+
+            with self.assertWarnsMessage(
+                DeprecationWarning, "This function is deprecated."
+            ):
+                sha2 = SHA2("d", hash_len)
+            ab = Alphabet.objects.annotate(sha=sha2).first()
+
             assert ab.sha == pysha
 
     def test_sha2_string_hash_len_default(self):
         string = "A string"
         Alphabet.objects.create(d=string)
         pysha512 = hashlib.sha512(string.encode("ascii")).hexdigest()
-        ab = Alphabet.objects.annotate(sha=SHA2("d")).first()
+
+        with self.assertWarnsMessage(
+            DeprecationWarning, "This function is deprecated."
+        ):
+            sha2 = SHA2("d")
+        ab = Alphabet.objects.annotate(sha=sha2).first()
+
         assert ab.sha == pysha512
 
     def test_sha2_bad_hash_len(self):