File tree Expand file tree Collapse file tree 5 files changed +65
-3
lines changed
docs/src/piccolo/query_types Expand file tree Collapse file tree 5 files changed +65
-3
lines changed Original file line number Diff line number Diff line change @@ -10,6 +10,22 @@ This deletes any matching rows from the table.
1010 >> > Band.delete().where(Band.name == ' Rustaceans' ).run_sync()
1111 []
1212
13+ force
14+ -----
15+
16+ Piccolo won't let you run a delete query without a where clause, unless you
17+ explicitly tell it to do so. This is to help prevent accidentally deleting all
18+ the data from a table.
19+
20+ .. code-block :: python
21+
22+ >> > Band.delete().run_sync()
23+ Raises: DeletionError
24+
25+ # Works fine:
26+ >> > Band.delete(force = True ).run_sync()
27+ []
28+
1329 Query clauses
1430-------------
1531
Original file line number Diff line number Diff line change @@ -74,7 +74,17 @@ def _process_results(self, results):
7474
7575 return raw
7676
77+ def _validate (self ):
78+ """
79+ Override in any subclasses if validation needs to be run before
80+ executing a query - for example, warning a user if they're about to
81+ delete all the data from a table.
82+ """
83+ pass
84+
7785 async def run (self , in_pool = True ):
86+ self ._validate ()
87+
7888 engine = getattr (self .table ._meta , "db" , None )
7989 if not engine :
8090 raise ValueError (
Original file line number Diff line number Diff line change 66from piccolo .querystring import QueryString
77
88
9+ class DeletionError (Exception ):
10+ pass
11+
12+
913class Delete (Query ):
1014
11- __slots__ = ("where_delegate" ,)
15+ __slots__ = ("where_delegate" , "force" )
16+
17+ def __init__ (self , force = False , * args , ** kwargs ):
18+ super ().__init__ (* args , ** kwargs )
19+ self .force = force
1220
1321 def setup_delegates (self ):
1422 self .where_delegate = WhereDelegate ()
@@ -17,6 +25,18 @@ def where(self, where: Combinable) -> Delete:
1725 self .where_delegate .where (where )
1826 return self
1927
28+ def _validate (self ):
29+ """
30+ Don't let a deletion happen unless it has a where clause, or is
31+ explicitly forced.
32+ """
33+ if (not self .where_delegate ._where ) and (not self .force ):
34+ raise DeletionError (
35+ "Warning - do you really want to delete all the data from "
36+ f"{ self .table ._meta .tablename } ? If so, use "
37+ "MyTable.delete(force=True), or add a where clause."
38+ )
39+
2040 @property
2141 def querystring (self ) -> QueryString :
2242 query = f"DELETE FROM { self .table ._meta .tablename } "
Original file line number Diff line number Diff line change @@ -295,11 +295,16 @@ def select(cls) -> Select:
295295 return Select (table = cls )
296296
297297 @classmethod
298- def delete (cls ) -> Delete :
298+ def delete (cls , force = False ) -> Delete :
299299 """
300+ Delete rows from the table.
301+
300302 await Band.delete().where(Band.name == 'Pythonistas').run()
303+
304+ Unless 'force' is set to True, deletions aren't allowed without a
305+ 'where' clause, to prevent accidental mass deletions.
301306 """
302- return Delete (table = cls )
307+ return Delete (table = cls , force = force )
303308
304309 @classmethod
305310 def create (cls ) -> Create :
Original file line number Diff line number Diff line change 1+ from piccolo .query .methods .delete import DeletionError
2+
13from ..base import DBTestCase
24from ..example_project .tables import Band
35
@@ -12,3 +14,12 @@ def test_delete(self):
1214 print (f"response = { response } " )
1315
1416 self .assertEqual (response , 0 )
17+
18+ def test_validation (self ):
19+ """
20+ Make sure you can't delete all the data without forcing it.
21+ """
22+ with self .assertRaises (DeletionError ):
23+ Band .delete ().run_sync ()
24+
25+ Band .delete (force = True ).run_sync ()
You can’t perform that action at this time.
0 commit comments