Skip to content

Commit a0e90be

Browse files
get_or_create method (piccolo-orm#181)
* very simple draft of `get_or_create` * add `GetOrCreate` and update docs * added empty dict as default, and allow direct await for convenience Co-authored-by: Daniel Townsend <[email protected]>
1 parent bf63f91 commit a0e90be

File tree

3 files changed

+88
-0
lines changed

3 files changed

+88
-0
lines changed

docs/src/piccolo/query_types/objects.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,23 @@ object, you can do so using ``get_related``.
8989
>>> print(manager.name)
9090
'Guido'
9191
92+
get_or_create
93+
-------------
94+
95+
With ``get_or_create`` you can get an existing record matching the criteria,
96+
or create a new one with the ``defaults`` arguments:
97+
98+
.. code-block:: python
99+
100+
band = Band.objects().get_or_create(
101+
Band.name == 'Pythonistas', defaults={Band.popularity: 100}
102+
).run_sync()
103+
104+
# Or using string column names
105+
band = Band.objects().get_or_create(
106+
Band.name == 'Pythonistas', defaults={'popularity': 100}
107+
).run_sync()
108+
92109
Query clauses
93110
-------------
94111

piccolo/query/methods/objects.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
WhereDelegate,
1515
)
1616
from piccolo.querystring import QueryString
17+
from piccolo.utils.sync import run_sync
1718

1819
from .select import Select
1920

@@ -22,6 +23,44 @@
2223
from piccolo.table import Table
2324

2425

26+
@dataclass
27+
class GetOrCreate:
28+
query: Objects
29+
where: Combinable
30+
defaults: t.Dict[t.Union[Column, str], t.Any]
31+
32+
async def run(self):
33+
instance = await self.query.where(self.where).first().run()
34+
if instance:
35+
return instance
36+
37+
instance = self.query.table()
38+
setattr(
39+
instance,
40+
self.where.column._meta.name, # type: ignore
41+
self.where.value, # type: ignore
42+
)
43+
44+
for column, value in self.defaults.items():
45+
if isinstance(column, str):
46+
column = instance._meta.get_column_by_name(column)
47+
setattr(instance, column._meta.name, value)
48+
49+
await instance.save().run()
50+
51+
return instance
52+
53+
def __await__(self):
54+
"""
55+
If the user doesn't explicity call .run(), proxy to it as a
56+
convenience.
57+
"""
58+
return self.run().__await__()
59+
60+
def run_sync(self):
61+
return run_sync(self.run())
62+
63+
2564
@dataclass
2665
class Objects(Query):
2766
"""
@@ -64,6 +103,13 @@ def offset(self, number: int) -> Objects:
64103
self.offset_delegate.offset(number)
65104
return self
66105

106+
def get_or_create(
107+
self,
108+
where: Combinable,
109+
defaults: t.Dict[t.Union[Column, str], t.Any] = {},
110+
):
111+
return GetOrCreate(query=self, where=where, defaults=defaults)
112+
67113
def order_by(self, *columns: Column, ascending=True) -> Objects:
68114
self.order_by_delegate.order_by(*columns, ascending=ascending)
69115
return self

tests/table/test_objects.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,28 @@ def test_offset_sqlite(self):
5959
self.assertEqual(
6060
[i.name for i in response], ["Pythonistas", "Rustaceans"]
6161
)
62+
63+
def test_get_or_create(self):
64+
Band.objects().get_or_create(
65+
Band.name == "Pink Floyd", defaults={"popularity": 100}
66+
).run_sync()
67+
68+
instance = (
69+
Band.objects().where(Band.name == "Pink Floyd").first().run_sync()
70+
)
71+
72+
self.assertTrue(isinstance(instance, Band))
73+
self.assertTrue(instance.name == "Pink Floyd")
74+
self.assertTrue(instance.popularity == 100)
75+
76+
Band.objects().get_or_create(
77+
Band.name == "Pink Floyd", defaults={Band.popularity: 100}
78+
).run_sync()
79+
80+
instance = (
81+
Band.objects().where(Band.name == "Pink Floyd").first().run_sync()
82+
)
83+
84+
self.assertTrue(isinstance(instance, Band))
85+
self.assertTrue(instance.name == "Pink Floyd")
86+
self.assertTrue(instance.popularity == 100)

0 commit comments

Comments
 (0)