From 14fe696a49d4055fed98ea5f75907ea14e714549 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 25 Aug 2023 16:56:36 +0200 Subject: [PATCH] Add delete function. It could be implemented with the ORM. However, we have everything needed to implement it with raw SQL; it's simpler, especially when it comes to exception handling; and there's no need to pull the big guns here. --- README.rst | 53 ++++++++++++++++++++++++++++++++++++--- src/sequences/__init__.py | 46 +++++++++++++++++++++++++++++++-- tests/test_sequences.py | 16 +++++++++--- 3 files changed, 106 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index f23fe26..cee90cf 100644 --- a/README.rst +++ b/README.rst @@ -244,6 +244,40 @@ The complete signature of ``get_last_value`` is: sequence generated but it makes no guarantees.** Concurrent calls to ``get_next_value`` may produce unexpected results of ``get_last_value``. +``delete`` +---------- + +.. code:: pycon + + >>> from sequences import delete + +This function deletes a sequence. It returns ``True`` if the sequence existed +and was deleted and ``False`` otherwise. + +.. code:: pycon + + >>> company_id = "b1f6cdef-367f-49e4-9cf5-bb0d34707af8" + >>> get_next_value(f"invoices—{company_id}") + 1 + >>> delete(f"invoices—{company_id}") + True + >>> delete(f"invoices—{company_id}") + False + +It accepts ``using="..."`` for selecting the database, like ``get_next_value``. + +The complete signature of ``delete`` is: + +.. code:: python + + delete( + sequence_name="default", + *, + using=None, + ) + +``delete`` is useful when you create many sequences and want to dispose of some. + ``Sequence`` ------------ @@ -253,8 +287,8 @@ sequence generated but it makes no guarantees.** Concurrent calls to (not to be confused with ``sequences.models.Sequence``, a private API) -This class stores parameters for a sequence and provides ``get_next_value`` -and ``get_last_value`` methods: +This class stores parameters for a sequence and provides ``get_next_value``, +``get_last_value``, and ``delete`` methods: .. code:: pycon @@ -265,6 +299,8 @@ and ``get_last_value`` methods: 2 >>> claim_ids.get_last_value() 2 + >>> claim_ids.delete() + True This reduces the risk of errors when the same sequence is used in multiple places. @@ -300,8 +336,12 @@ The complete API is: self, ) -All parameters have the same meaning as in the ``get_next_value`` and -``get_last_value`` functions. + Sequence.delete( + self, + ) + +All parameters have the same meaning as in the ``get_next_value``, +``get_last_value``, and ``delete`` functions. Examples ======== @@ -458,6 +498,11 @@ Build and publish the new version: Changelog ========= +2.9 +--- + +* Add the ``delete`` function. + 2.8 --- diff --git a/src/sequences/__init__.py b/src/sequences/__init__.py index c55b8ac..91f6c26 100644 --- a/src/sequences/__init__.py +++ b/src/sequences/__init__.py @@ -21,6 +21,12 @@ UPDATE last = {db_table}.last + 1 """ +DELETE = """ + DELETE + FROM {db_table} + WHERE name = %s +""" + def get_last_value( sequence_name="default", @@ -28,7 +34,7 @@ def get_last_value( using=None, ): """ - Return the last value for a given sequence. + Return the last value for a sequence. """ # Inner import because models cannot be imported before their application. @@ -59,7 +65,7 @@ def get_next_value( using=None, ): """ - Return the next value for a given sequence. + Return the next value for a sequence. """ # Inner import because models cannot be imported before their application. @@ -124,6 +130,32 @@ def get_next_value( return sequence.last +def delete( + sequence_name="default", + *, + using=None, +): + """ + Delete a sequence. + + """ + # Inner import because models cannot be imported before their application. + from .models import Sequence + + if using is None: + using = router.db_for_write(Sequence) + + connection = connections[using] + db_table = connection.ops.quote_name(Sequence._meta.db_table) + + with connection.cursor() as cursor: + cursor.execute( + DELETE.format(db_table=db_table), + [sequence_name], + ) + return bool(cursor.rowcount) + + class Sequence: """ Generate a gapless sequence of integer values. @@ -168,6 +200,16 @@ def get_next_value(self, *, nowait=False): using=self.using, ) + def delete(self): + """ + Delete the sequence. + + """ + return delete( + self.sequence_name, + using=self.using, + ) + def __iter__(self): return self diff --git a/tests/test_sequences.py b/tests/test_sequences.py index b09e578..5669a5b 100644 --- a/tests/test_sequences.py +++ b/tests/test_sequences.py @@ -6,7 +6,7 @@ from django.db import DatabaseError, connection, transaction from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature -from sequences import Sequence, get_last_value, get_next_value +from sequences import Sequence, delete, get_last_value, get_next_value class SingleConnectionTestsMixin: @@ -16,7 +16,10 @@ def test_functions_defaults(self): self.assertEqual(get_last_value(), 1) self.assertEqual(get_next_value(), 2) self.assertEqual(get_last_value(), 2) - self.assertEqual(get_next_value(), 3) + self.assertEqual(delete(), True) + self.assertEqual(get_last_value(), None) + self.assertEqual(delete(), False) + self.assertEqual(get_last_value(), None) def test_functions_sequence_name(self): self.assertEqual(get_last_value("cases"), None) @@ -55,7 +58,10 @@ def test_class_defaults(self): self.assertEqual(seq.get_last_value(), 1) self.assertEqual(seq.get_next_value(), 2) self.assertEqual(seq.get_last_value(), 2) - self.assertEqual(seq.get_next_value(), 3) + self.assertEqual(seq.delete(), True) + self.assertEqual(seq.get_last_value(), None) + self.assertEqual(seq.delete(), False) + self.assertEqual(seq.get_last_value(), None) def test_class_iter(self): seq = Sequence(initial_value=0) @@ -77,6 +83,10 @@ def test_class_sequence_name(self): self.assertEqual(invoices_seq.get_next_value(), 1) self.assertEqual(invoices_seq.get_last_value(), 1) self.assertEqual(invoices_seq.get_next_value(), 2) + self.assertEqual(cases_seq.delete(), True) + self.assertEqual(cases_seq.get_last_value(), None) + self.assertEqual(invoices_seq.delete(), True) + self.assertEqual(invoices_seq.get_last_value(), None) def test_class_initial_value(self): customers_seq = Sequence("customers", initial_value=1000)