piccolo 0.113.0__py3-none-any.whl → 0.115.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
piccolo/__init__.py CHANGED
@@ -1 +1 @@
1
- __VERSION__ = "0.113.0"
1
+ __VERSION__ = "0.115.0"
@@ -1,19 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import sys
3
4
  import typing as t
4
5
 
6
+ import typing_extensions
7
+
5
8
  from piccolo.apps.fixtures.commands.shared import (
6
9
  FixtureConfig,
7
10
  create_pydantic_fixture_model,
8
11
  )
9
12
  from piccolo.conf.apps import Finder
10
13
  from piccolo.engine import engine_finder
14
+ from piccolo.query.mixins import OnConflictAction
11
15
  from piccolo.table import Table, sort_table_classes
12
16
  from piccolo.utils.encoding import load_json
13
17
  from piccolo.utils.list import batch
14
18
 
15
19
 
16
- async def load_json_string(json_string: str, chunk_size: int = 1000):
20
+ async def load_json_string(
21
+ json_string: str,
22
+ chunk_size: int = 1000,
23
+ on_conflict_action: t.Optional[OnConflictAction] = None,
24
+ ):
17
25
  """
18
26
  Parses the JSON string, and inserts the parsed data into the database.
19
27
  """
@@ -71,10 +79,23 @@ async def load_json_string(json_string: str, chunk_size: int = 1000):
71
79
  rows = data[table_class]
72
80
 
73
81
  for chunk in batch(data=rows, chunk_size=chunk_size):
74
- await table_class.insert(*chunk).run()
75
-
76
-
77
- async def load(path: str = "fixture.json", chunk_size: int = 1000):
82
+ query = table_class.insert(*chunk)
83
+ if on_conflict_action is not None:
84
+ query = query.on_conflict(
85
+ target=table_class._meta.primary_key,
86
+ action=on_conflict_action,
87
+ values=table_class._meta.columns,
88
+ )
89
+ await query.run()
90
+
91
+
92
+ async def load(
93
+ path: str = "fixture.json",
94
+ chunk_size: int = 1000,
95
+ on_conflict: t.Optional[
96
+ typing_extensions.Literal["DO NOTHING", "DO UPDATE"]
97
+ ] = None,
98
+ ):
78
99
  """
79
100
  Reads the fixture file, and loads the contents into the database.
80
101
 
@@ -86,8 +107,28 @@ async def load(path: str = "fixture.json", chunk_size: int = 1000):
86
107
  determined by the database adapter, which has a max number of
87
108
  parameters per query.
88
109
 
110
+ :param on_conflict:
111
+ If specified, the fixture will be upserted, meaning that if a row
112
+ already exists with a matching primary key, then it will be overridden
113
+ if "DO UPDATE", or it will be ignored if "DO NOTHING".
114
+
89
115
  """
90
116
  with open(path, "r") as f:
91
117
  contents = f.read()
92
118
 
93
- await load_json_string(contents, chunk_size=chunk_size)
119
+ on_conflict_action: t.Optional[OnConflictAction] = None
120
+
121
+ if on_conflict:
122
+ try:
123
+ on_conflict_action = OnConflictAction(on_conflict.upper())
124
+ except ValueError:
125
+ sys.exit(
126
+ f"{on_conflict} isn't a valid option - use 'DO NOTHING' or "
127
+ "'DO UPDATE'."
128
+ )
129
+
130
+ await load_json_string(
131
+ contents,
132
+ chunk_size=chunk_size,
133
+ on_conflict_action=on_conflict_action,
134
+ )
@@ -826,7 +826,7 @@ class MigrationManager:
826
826
  await self._run_query(
827
827
  schema_manager.move_table(
828
828
  table_name=change_table_schema.tablename,
829
- new_schema=change_table_schema.new_schema,
829
+ new_schema=change_table_schema.new_schema or "public",
830
830
  current_schema=change_table_schema.old_schema,
831
831
  )
832
832
  )
piccolo/columns/m2m.py CHANGED
@@ -57,30 +57,34 @@ class M2MSelect(Selectable):
57
57
  )
58
58
 
59
59
  def get_select_string(self, engine_type: str, with_alias=True) -> str:
60
- m2m_table_name = self.m2m._meta.resolved_joining_table._meta.tablename
60
+ m2m_table_name_with_schema = (
61
+ self.m2m._meta.resolved_joining_table._meta.get_formatted_tablename() # noqa: E501
62
+ ) # noqa: E501
61
63
  m2m_relationship_name = self.m2m._meta.name
62
64
 
63
65
  fk_1 = self.m2m._meta.primary_foreign_key
64
66
  fk_1_name = fk_1._meta.db_column_name
65
67
  table_1 = fk_1._foreign_key_meta.resolved_references
66
68
  table_1_name = table_1._meta.tablename
69
+ table_1_name_with_schema = table_1._meta.get_formatted_tablename()
67
70
  table_1_pk_name = table_1._meta.primary_key._meta.db_column_name
68
71
 
69
72
  fk_2 = self.m2m._meta.secondary_foreign_key
70
73
  fk_2_name = fk_2._meta.db_column_name
71
74
  table_2 = fk_2._foreign_key_meta.resolved_references
72
75
  table_2_name = table_2._meta.tablename
76
+ table_2_name_with_schema = table_2._meta.get_formatted_tablename()
73
77
  table_2_pk_name = table_2._meta.primary_key._meta.db_column_name
74
78
 
75
79
  inner_select = f"""
76
- "{m2m_table_name}"
77
- JOIN "{table_1_name}" "inner_{table_1_name}" ON (
78
- "{m2m_table_name}"."{fk_1_name}" = "inner_{table_1_name}"."{table_1_pk_name}"
80
+ {m2m_table_name_with_schema}
81
+ JOIN {table_1_name_with_schema} "inner_{table_1_name}" ON (
82
+ {m2m_table_name_with_schema}."{fk_1_name}" = "inner_{table_1_name}"."{table_1_pk_name}"
79
83
  )
80
- JOIN "{table_2_name}" "inner_{table_2_name}" ON (
81
- "{m2m_table_name}"."{fk_2_name}" = "inner_{table_2_name}"."{table_2_pk_name}"
84
+ JOIN {table_2_name_with_schema} "inner_{table_2_name}" ON (
85
+ {m2m_table_name_with_schema}."{fk_2_name}" = "inner_{table_2_name}"."{table_2_pk_name}"
82
86
  )
83
- WHERE "{m2m_table_name}"."{fk_1_name}" = "{table_1_name}"."{table_1_pk_name}"
87
+ WHERE {m2m_table_name_with_schema}."{fk_1_name}" = "{table_1_name}"."{table_1_pk_name}"
84
88
  """ # noqa: E501
85
89
 
86
90
  if engine_type in ("postgres", "cockroach"):
@@ -4,19 +4,29 @@ import typing as t
4
4
 
5
5
  from piccolo.custom_types import Combinable
6
6
  from piccolo.query.base import Query
7
- from piccolo.query.methods.select import Select
7
+ from piccolo.query.methods.select import Count as SelectCount
8
8
  from piccolo.query.mixins import WhereDelegate
9
9
  from piccolo.querystring import QueryString
10
10
 
11
11
  if t.TYPE_CHECKING: # pragma: no cover
12
+ from piccolo.columns import Column
12
13
  from piccolo.table import Table
13
14
 
14
15
 
15
16
  class Count(Query):
16
- __slots__ = ("where_delegate",)
17
17
 
18
- def __init__(self, table: t.Type[Table], **kwargs):
18
+ __slots__ = ("where_delegate", "column", "_distinct")
19
+
20
+ def __init__(
21
+ self,
22
+ table: t.Type[Table],
23
+ column: t.Optional[Column] = None,
24
+ distinct: t.Optional[t.Sequence[Column]] = None,
25
+ **kwargs,
26
+ ):
19
27
  super().__init__(table, **kwargs)
28
+ self.column = column
29
+ self._distinct = distinct
20
30
  self.where_delegate = WhereDelegate()
21
31
 
22
32
  ###########################################################################
@@ -26,6 +36,10 @@ class Count(Query):
26
36
  self.where_delegate.where(*where)
27
37
  return self
28
38
 
39
+ def distinct(self: Self, columns: t.Optional[t.Sequence[Column]]) -> Self:
40
+ self._distinct = columns
41
+ return self
42
+
29
43
  ###########################################################################
30
44
 
31
45
  async def response_handler(self, response) -> bool:
@@ -33,14 +47,15 @@ class Count(Query):
33
47
 
34
48
  @property
35
49
  def default_querystrings(self) -> t.Sequence[QueryString]:
36
- select = Select(self.table)
37
- select.where_delegate._where = self.where_delegate._where
38
- return [
39
- QueryString(
40
- 'SELECT COUNT(*) AS "count" FROM ({}) AS "subquery"',
41
- select.querystrings[0],
42
- )
43
- ]
50
+ table: t.Type[Table] = self.table
51
+
52
+ query = table.select(
53
+ SelectCount(column=self.column, distinct=self._distinct)
54
+ )
55
+
56
+ query.where_delegate._where = self.where_delegate._where
57
+
58
+ return query.querystrings
44
59
 
45
60
 
46
61
  Self = t.TypeVar("Self", bound=Count)
@@ -99,43 +99,84 @@ class Avg(Selectable):
99
99
 
100
100
  class Count(Selectable):
101
101
  """
102
- Used in conjunction with the ``group_by`` clause in ``Select`` queries.
102
+ Used in ``Select`` queries, usually in conjunction with the ``group_by``
103
+ clause::
103
104
 
104
- If a column is specified, the count is for non-null values in that
105
- column. If no column is specified, the count is for all rows, whether
106
- they have null values or not.
105
+ >>> await Band.select(
106
+ ... Band.manager.name.as_alias('manager_name'),
107
+ ... Count(alias='band_count')
108
+ ... ).group_by(Band.manager)
109
+ [{'manager_name': 'Guido', 'count': 1}, ...]
107
110
 
108
- .. code-block:: python
109
-
110
- await Band.select(Band.name, Count()).group_by(Band.name)
111
-
112
- # We can use an alias. These two are equivalent:
113
-
114
- await Band.select(
115
- Band.name, Count(alias="total")
116
- ).group_by(Band.name)
111
+ It can also be used without the ``group_by`` clause (though you may prefer
112
+ to the :meth:`Table.count <piccolo.table.Table.count>` method instead, as
113
+ it's more convenient)::
117
114
 
118
- await Band.select(
119
- Band.name,
120
- Count().as_alias("total")
121
- ).group_by(Band.name)
115
+ >>> await Band.select(Count())
116
+ [{'count': 3}]
122
117
 
123
118
  """
124
119
 
125
120
  def __init__(
126
- self, column: t.Optional[Column] = None, alias: str = "count"
121
+ self,
122
+ column: t.Optional[Column] = None,
123
+ distinct: t.Optional[t.Sequence[Column]] = None,
124
+ alias: str = "count",
127
125
  ):
126
+ """
127
+ :param column:
128
+ If specified, the count is for non-null values in that column.
129
+ :param distinct:
130
+ If specified, the count is for distinct values in those columns.
131
+ :param alias:
132
+ The name of the value in the response::
133
+
134
+ # These two are equivalent:
135
+
136
+ await Band.select(
137
+ Band.name, Count(alias="total")
138
+ ).group_by(Band.name)
139
+
140
+ await Band.select(
141
+ Band.name,
142
+ Count().as_alias("total")
143
+ ).group_by(Band.name)
144
+
145
+ """
146
+ if distinct and column:
147
+ raise ValueError("Only specify `column` or `distinct`")
148
+
128
149
  self.column = column
150
+ self.distinct = distinct
129
151
  self._alias = alias
130
152
 
131
153
  def get_select_string(
132
154
  self, engine_type: str, with_alias: bool = True
133
155
  ) -> str:
134
- if self.column is None:
135
- column_name = "*"
156
+ expression: str
157
+
158
+ if self.distinct:
159
+ if engine_type == "sqlite":
160
+ # SQLite doesn't allow us to specify multiple columns, so
161
+ # instead we concatenate the values.
162
+ column_names = " || ".join(
163
+ i._meta.get_full_name(with_alias=False)
164
+ for i in self.distinct
165
+ )
166
+ else:
167
+ column_names = ", ".join(
168
+ i._meta.get_full_name(with_alias=False)
169
+ for i in self.distinct
170
+ )
171
+
172
+ expression = f"DISTINCT ({column_names})"
136
173
  else:
137
- column_name = self.column._meta.get_full_name(with_alias=False)
138
- return f'COUNT({column_name}) AS "{self._alias}"'
174
+ if self.column:
175
+ expression = self.column._meta.get_full_name(with_alias=False)
176
+ else:
177
+ expression = "*"
178
+
179
+ return f'COUNT({expression}) AS "{self._alias}"'
139
180
 
140
181
 
141
182
  class Max(Selectable):
@@ -737,12 +778,10 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
737
778
  query = "SELECT"
738
779
 
739
780
  distinct = self.distinct_delegate._distinct
740
- if distinct:
741
- if distinct.on:
742
- distinct.validate_on(self.order_by_delegate._order_by)
743
-
744
- query += "{}"
745
- args.append(distinct.querystring)
781
+ if distinct.on:
782
+ distinct.validate_on(self.order_by_delegate._order_by)
783
+ query += "{}"
784
+ args.append(distinct.querystring)
746
785
 
747
786
  query += f" {columns_str} FROM {self.table._meta.get_formatted_tablename()}" # noqa: E501
748
787
 
piccolo/table.py CHANGED
@@ -1114,16 +1114,54 @@ class Table(metaclass=TableMetaclass):
1114
1114
  return Objects[TableInstance](table=cls, prefetch=prefetch)
1115
1115
 
1116
1116
  @classmethod
1117
- def count(cls) -> Count:
1118
- """
1119
- Count the number of matching rows.
1117
+ def count(
1118
+ cls,
1119
+ column: t.Optional[Column] = None,
1120
+ distinct: t.Optional[t.Sequence[Column]] = None,
1121
+ ) -> Count:
1120
1122
 
1121
- .. code-block:: python
1123
+ """
1124
+ Count the number of matching rows::
1122
1125
 
1123
1126
  await Band.count().where(Band.popularity > 1000)
1124
1127
 
1128
+ :param column:
1129
+ If specified, just count rows where this column isn't null.
1130
+
1131
+ :param distinct:
1132
+ Counts the number of distinct values for these columns. For
1133
+ example, if we have a concerts table::
1134
+
1135
+ class Concert(Table):
1136
+ band = Varchar()
1137
+ start_date = Date()
1138
+
1139
+ With this data:
1140
+
1141
+ .. table::
1142
+ :widths: auto
1143
+
1144
+ =========== ==========
1145
+ band start_date
1146
+ =========== ==========
1147
+ Pythonistas 2023-01-01
1148
+ Pythonistas 2023-02-03
1149
+ Rustaceans 2023-01-01
1150
+ =========== ==========
1151
+
1152
+ Without the ``distinct`` argument, we get the count of all
1153
+ rows::
1154
+
1155
+ >>> await Concert.count()
1156
+ 3
1157
+
1158
+ To get the number of unique concert dates::
1159
+
1160
+ >>> await Concert.count(distinct=[Concert.start_date])
1161
+ 2
1162
+
1125
1163
  """
1126
- return Count(table=cls)
1164
+ return Count(table=cls, column=column, distinct=distinct)
1127
1165
 
1128
1166
  @classmethod
1129
1167
  def exists(cls) -> Exists:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piccolo
3
- Version: 0.113.0
3
+ Version: 0.115.0
4
4
  Summary: A fast, user friendly ORM and query builder which supports asyncio.
5
5
  Home-page: https://github.com/piccolo-orm/piccolo
6
6
  Author: Daniel Townsend
@@ -1,10 +1,10 @@
1
- piccolo/__init__.py,sha256=bbQ6ntboeYUZlRuUo9VeWe-Il4YWTUroehQvy5v58yc,24
1
+ piccolo/__init__.py,sha256=-CLJ49iCidYB3xsIzg9Dqc7jRuCmSb4xu1RlklEi9y8,24
2
2
  piccolo/custom_types.py,sha256=7HMQAze-5mieNLfbQ5QgbRQgR2abR7ol0qehv2SqROY,604
3
3
  piccolo/main.py,sha256=2W2EXXEr-EN1PG8s8xHIWCvU7t7kT004fBChK9CZhzo,5024
4
4
  piccolo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  piccolo/querystring.py,sha256=pjtp0jLtZc7AaVESR4QTZu9wlsGGxeyfLjJZx26U7IA,5859
6
6
  piccolo/schema.py,sha256=aWPuZxEulgBRD5NTqKN-RAZchxu-PoIrn0iFrWGZuq4,7731
7
- piccolo/table.py,sha256=64L2bvAaKLK2qWlYgzyXJhBNiyT4o08VnrkT8W8vcCg,48231
7
+ piccolo/table.py,sha256=99O73bkpvI5tzFSMD54yOvTa7Mha3mWjWrSL0ILpTsA,49374
8
8
  piccolo/table_reflection.py,sha256=jrN1nHerDJ4tU09GtNN3hz7ap-7rXnSUjljFO6LB2H0,7094
9
9
  piccolo/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  piccolo/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -45,7 +45,7 @@ piccolo/apps/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
45
45
  piccolo/apps/fixtures/piccolo_app.py,sha256=4O1Cznl1zms2gIw2iVjCjidkgCfFcB83nZIAJwcNTtg,268
46
46
  piccolo/apps/fixtures/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  piccolo/apps/fixtures/commands/dump.py,sha256=fC0XsLaTLAP_VtfFSWPNj9XsZLfWGpl-Og_y71_OL7o,3650
48
- piccolo/apps/fixtures/commands/load.py,sha256=1uCFZeeBXov6CnoX66q4DEre1HUgq9ih9R03Y8IGSy0,2945
48
+ piccolo/apps/fixtures/commands/load.py,sha256=nqjqrOrEww5Q_gvhTP_Nhu4urD6JqwpsC3NR9FWZq7o,4188
49
49
  piccolo/apps/fixtures/commands/shared.py,sha256=BMG8ku5FyK5vLewaDYwEfe_5HCnEVz1oKxHfL-GpO08,1462
50
50
  piccolo/apps/meta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  piccolo/apps/meta/piccolo_app.py,sha256=EdAB74BvwpwO9L8DOIMXPly9f4hiW3oaWdzoLNNV2a4,224
@@ -56,7 +56,7 @@ piccolo/apps/migrations/piccolo_app.py,sha256=1EcS2ComBPCaMCC2C3WaPR_GqLwt3XiIJN
56
56
  piccolo/apps/migrations/tables.py,sha256=jqBnK-Rk545v1Eu6GaLHTVz7-uwBTUnz2m58OA-mxTc,799
57
57
  piccolo/apps/migrations/auto/__init__.py,sha256=5bgdh5EsROSvq4rrZAxpzmXiJNj5LJKwf06dxI2LZsc,191
58
58
  piccolo/apps/migrations/auto/diffable_table.py,sha256=ZjUIjqcEAWsoFJuShSDy6W1HUBOM1G7kEsne-R5VJ-Q,6935
59
- piccolo/apps/migrations/auto/migration_manager.py,sha256=5sRoH_WUWfG0MSC7dvJbSxW60Uoscaqx_UeFug7sxQg,31753
59
+ piccolo/apps/migrations/auto/migration_manager.py,sha256=RqegV19ZM2hcb03x5IBP2615CD1uEhrgPXKNZIZjq5o,31765
60
60
  piccolo/apps/migrations/auto/operations.py,sha256=eAiTK9wCxFwhE13W0Tf2-FUg7kyPUWjDEGHyyRHwZUg,1154
61
61
  piccolo/apps/migrations/auto/schema_differ.py,sha256=oNJVqqDxokUhMU-JjoA0aC21j0sbLEHMZRiyQwGG-rg,25348
62
62
  piccolo/apps/migrations/auto/schema_snapshot.py,sha256=ZqUg4NpChOeoACKF2gkhqsz1BW3wOWFnzJCccq-CNNQ,4719
@@ -115,7 +115,7 @@ piccolo/columns/choices.py,sha256=-HNQuk9vMmVZIPZ5PMeXGTfr23o4nzKPSAkvcG1k0y8,72
115
115
  piccolo/columns/column_types.py,sha256=X0nTvlc8epfcu8Q8uuIBlnHv_R_GTJ58dehC343vNgA,77278
116
116
  piccolo/columns/combination.py,sha256=vMXC2dfY7pvnCFhsT71XFVyb4gdQzfRsCMaiduu04Ss,6900
117
117
  piccolo/columns/indexes.py,sha256=NfNok3v_791jgDlN28KmhP9ZCjl6031BXmjxV3ovXJk,372
118
- piccolo/columns/m2m.py,sha256=C7IKMg7ik2yE3143Gwdbx3YNB3VrZbltJAlX0XxQwAI,14067
118
+ piccolo/columns/m2m.py,sha256=JNgulzZjmwzPDNlpHLMIE_GP8UoBEcWFS4mEIkbKpvk,14357
119
119
  piccolo/columns/readable.py,sha256=vKIEc_vWxKo4GSPOeJZz-q5a1i4DNaoBcCNG_i_2OSA,1485
120
120
  piccolo/columns/reference.py,sha256=-e1nu-yWeqiyJtDqG0cPVVVrLai6ClEAYAa8c6a0hnA,3573
121
121
  piccolo/columns/defaults/__init__.py,sha256=7hpB13baEJgc1zbZjRKDFr-5hltxM2VGj8KnKfOiS8c,145
@@ -146,7 +146,7 @@ piccolo/query/mixins.py,sha256=N6HAN_A4kd-PC07q3OIzwrkRy3ZwGMtB2xLueYefBSM,21649
146
146
  piccolo/query/proxy.py,sha256=Hg5S6tp1EiKD899eYdDKHscFYucHdKtL3YC2GTcL2Jk,1833
147
147
  piccolo/query/methods/__init__.py,sha256=_PfGUdOd6AsKq1sqXeZUHhESHE-e1cNpwFr8Lyz7QoY,421
148
148
  piccolo/query/methods/alter.py,sha256=AI9YkJeip2EitrWJN_TDExXhA8HGAG3XuDz1NR-KirQ,16728
149
- piccolo/query/methods/count.py,sha256=SBQaOkCJhzHO-1dOUYJuUgWwygc9uaDg-nupsXgrXHQ,1339
149
+ piccolo/query/methods/count.py,sha256=vSwn52IG0wlhPC6L-jYVlCsD4BPb2EkGHGwWn7z7gH4,1717
150
150
  piccolo/query/methods/create.py,sha256=hJ-6VVsWczzKDH6fQRN1WmYhcitixuXJ-eNOuCo_JgM,2742
151
151
  piccolo/query/methods/create_index.py,sha256=RV9yVHwPvfQCk-g6YpmUTKamgOj0uxWe8Zr97YHIPGo,2216
152
152
  piccolo/query/methods/delete.py,sha256=c4LO6-sGKfX-pi1nTZPC3aKvevgKWXuO28sFetbQ7WY,2212
@@ -157,7 +157,7 @@ piccolo/query/methods/insert.py,sha256=ygQQBHMEtZRpPDYKK9qv4mdJsCcSZOA0d5drwqb57
157
157
  piccolo/query/methods/objects.py,sha256=i71GHPJZJcRpgM6e69Vk7vVcCuAFokOsMnR5EXbZq1w,11673
158
158
  piccolo/query/methods/raw.py,sha256=VhYpCB52mZk4zqFTsqK5CHKTDGskUjISXTBV7UjohmA,600
159
159
  piccolo/query/methods/refresh.py,sha256=P1Eo_HYU_L7kcGM_cvDDgyLi1boCXY7Pc4tv_eDAzvc,2769
160
- piccolo/query/methods/select.py,sha256=5fEAilOzqJcXOKPbhnt_lzNhstZZl9SNQM0g5RiZmPs,25540
160
+ piccolo/query/methods/select.py,sha256=lkR46W2oatozkTgyKjACtpf8UVRZBISVAf607e2ljWs,26903
161
161
  piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
162
162
  piccolo/query/methods/update.py,sha256=KJrdS98uvbio_95h979xGd6HTtn9d0LP7SqwPjBn7tU,3655
163
163
  piccolo/testing/__init__.py,sha256=pRFSqRInfx95AakOq54atmvqoB-ue073q2aR8u8zR40,83
@@ -190,7 +190,7 @@ tests/apps/asgi/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
190
190
  tests/apps/asgi/commands/test_new.py,sha256=CxlY2TGK-fOAPUroKK4CIXRyBwnsetAehAAIc4wheUE,3097
191
191
  tests/apps/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
192
192
  tests/apps/fixtures/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
193
- tests/apps/fixtures/commands/test_dump_load.py,sha256=Ohnm_DZl1JLK_SI4g5THbm5M9-43hj40fdB3lefJrpM,8172
193
+ tests/apps/fixtures/commands/test_dump_load.py,sha256=aS4as-Uve4W65c2hL5oWvxYwL4HJrsD5gaUBzmzqm_Y,9344
194
194
  tests/apps/fixtures/commands/test_shared.py,sha256=z-0-lrkATlt98hJbu3udKne_DW7BT6p_iPZLjUuwyb4,2139
195
195
  tests/apps/meta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
196
196
  tests/apps/meta/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -204,7 +204,7 @@ tests/apps/migrations/auto/test_schema_differ.py,sha256=0P7TCUyggPuqmO5QnzoMu-u_
204
204
  tests/apps/migrations/auto/test_schema_snapshot.py,sha256=ZyvGZqn3N3cwd-3S-FME5AJ8buDSHesw7yPIvY6mE5k,6196
205
205
  tests/apps/migrations/auto/test_serialisation.py,sha256=EFkhES1w9h51UCamWrhxs3mf4I718ggeP7Yl5J_UID4,13548
206
206
  tests/apps/migrations/auto/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
207
- tests/apps/migrations/auto/integration/test_migrations.py,sha256=F9g5_hwtVyTZRh32tSp-ql9-FnhWx4HsBJUdcbYEraA,41986
207
+ tests/apps/migrations/auto/integration/test_migrations.py,sha256=9CM7PI79DS3CRjkXXiXcU5JhQR-CceTT2LfV4P-G7HI,42960
208
208
  tests/apps/migrations/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
209
209
  tests/apps/migrations/commands/test_base.py,sha256=NgHgVjNd3Hil9eODvW7Ic2D9muTa_grNaH3YpRFfR8I,1829
210
210
  tests/apps/migrations/commands/test_check.py,sha256=hOX_sVk1nfCRfbQ8tJoFEUBFhih9O4QuQLHTp5TQaiY,630
@@ -245,7 +245,6 @@ tests/columns/test_double_precision.py,sha256=YuC-mVMvwYgtiBzZDIajf6HbLkb08OWs0N
245
245
  tests/columns/test_interval.py,sha256=1kn0ufhAMe0vG7343rQyoQ2EDJ3o1ER8p3HCC1sSRhc,2921
246
246
  tests/columns/test_json.py,sha256=T5v7GD6sOro8X2OB3Nwhw8-BbsmKmbD9w3o4c8bzv8M,3814
247
247
  tests/columns/test_jsonb.py,sha256=YomSvGyLKfmXhJiFIE1StfDyPMBuXeEETZZcVQHbiwE,6821
248
- tests/columns/test_m2m.py,sha256=-WB52gOti74t1RsJJhUyKD2TAsYvmYVjj9UylAJp5zI,25767
249
248
  tests/columns/test_numeric.py,sha256=8h6LwYI_3Jer9R6w2Ew_xtH09JSPsDZ0ahVIXEKAnnM,813
250
249
  tests/columns/test_primary_key.py,sha256=KrV5sbH7MOZk0Uu8WtK8Pi3KtmZRVEmSKloN-DVFoKs,5396
251
250
  tests/columns/test_readable.py,sha256=kMuge5X_PfVeE1PjStwEHY-W0zwrxVBXV0CiW2Gw7kE,802
@@ -258,6 +257,10 @@ tests/columns/test_timestamp.py,sha256=q8idev7hO8TrFXeLb8ZVBFVBb3kqWck17s-RZP747
258
257
  tests/columns/test_timestamptz.py,sha256=IvBC4S1MLkYRG6jOGn9631qmwKkg2lqftC_8i71zDcA,2771
259
258
  tests/columns/test_uuid.py,sha256=HDh_qvznN7nUjCaJ-TJwRcPjMTYMLhrO_oOHtW9MAAc,466
260
259
  tests/columns/test_varchar.py,sha256=5IY9J5m1Hmuq1vZ78aApwPYI7J-XZFVtZVHPhlKGn8Y,813
260
+ tests/columns/m2m/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
261
+ tests/columns/m2m/base.py,sha256=PgdTs4bFHNH5V7SgfhkOwE3ch2XH8J-AbX7yo0DO7J4,14145
262
+ tests/columns/m2m/test_m2m.py,sha256=XD20-MCci7T2gSc7loI1JScsYpItdiwacrOxBK5BP8s,13084
263
+ tests/columns/m2m/test_m2m_schema.py,sha256=wTsTOEaAnReECeNfjP_lRojuMDFy4-_wQbZDkdsmsYw,1184
261
264
  tests/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
262
265
  tests/conf/example.py,sha256=kOa8EUzxG-iP-BGJuJv3-mqyvr1vW7XLVMeuCXzypo8,346
263
266
  tests/conf/test_apps.py,sha256=mqkkdEnFjphbrdRh9yKu4Red05-3l0fG2pOxYhT_9tU,7578
@@ -294,7 +297,7 @@ tests/table/test_alter.py,sha256=k_tXdiuG6grybRNKmXdt7PdwaENOsvoNX-lodB_hCF8,123
294
297
  tests/table/test_batch.py,sha256=5XdwZfsQWhnWSEMVG_yetW-28yYTU6bBCZJGYjx4sOU,3740
295
298
  tests/table/test_callback.py,sha256=mNp4NOQ4PM_PIKn-WLG4HyvN-YHk7w0Ka_Bljg5Ugd4,6381
296
299
  tests/table/test_constructor.py,sha256=zPbzhKQWzzsQQUK7P9WM8OgCi1ndhXedP6rU0tg6XIM,832
297
- tests/table/test_count.py,sha256=PIMKpoxRmo9SI4tfsJ6wIPIPDg7wlhdNrOuHtEZX80w,285
300
+ tests/table/test_count.py,sha256=qm4dwlQJ5gv8FPSsgYTS-3Gsd_KLgvWlFnmXweKydxw,2297
298
301
  tests/table/test_create.py,sha256=d-X7faDGI6NesvElu9JhrAo0Ialmrl6163Whd_LAUDQ,2887
299
302
  tests/table/test_create_db_tables.py,sha256=9ZVlv9jGX5uneMfM5c2j1LlOphgorFNHN1cQ1pay4gM,903
300
303
  tests/table/test_create_table_class.py,sha256=N56XnaixbiZlakAexdMOw6FI4XnJjy_ybVjpsyhgMQ0,1341
@@ -308,7 +311,7 @@ tests/table/test_insert.py,sha256=LeYNvApZ2T-PZ9fseINLZ6hnrY5F1Axe3QHqidwzbAQ,13
308
311
  tests/table/test_join.py,sha256=tk2r5OUaay9-4U37aj2-qul1XybchBG3xr-k7K_IQh0,14705
309
312
  tests/table/test_join_on.py,sha256=NhJRg_7_YQ0o2ox5mF330ZaIvmtq09Xl2lfDTwKtUng,2719
310
313
  tests/table/test_metaclass.py,sha256=pimcSDH8GYcTTmrwEz7agezgZPyb6_aUTi2CqwsLqFY,3562
311
- tests/table/test_objects.py,sha256=aEv-y0xkg6UMrWo8rd098ahCI7m177AL3dwU3JNMKRY,7895
314
+ tests/table/test_objects.py,sha256=kMvR-6vah0dU25lQSKlVJkjJOPX3Qd27d5mGKwmm1CY,7910
312
315
  tests/table/test_output.py,sha256=BvALFil1VlWKPmlRiqlrhhiAVXcuj3E-Bg85JMqGNlQ,2984
313
316
  tests/table/test_raw.py,sha256=9PTvYngQi41nYd5lKzkJdTqsEcwrdOXcvZjq-W26CwQ,1683
314
317
  tests/table/test_ref.py,sha256=eYNRnYHzNMXuMbV3B1ca5EidpIg4500q6hr1ccuVaso,269
@@ -341,9 +344,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
341
344
  tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
342
345
  tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
343
346
  tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
344
- piccolo-0.113.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
345
- piccolo-0.113.0.dist-info/METADATA,sha256=y3WXBB9-MXuwGvUy98op1YrvgE2mh4uYWa6F_c_F8tM,5113
346
- piccolo-0.113.0.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
347
- piccolo-0.113.0.dist-info/entry_points.txt,sha256=zYhu-YNtMlh2N_8wptCS8YWKOgc81UPL3Ji5gly8ouc,47
348
- piccolo-0.113.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
349
- piccolo-0.113.0.dist-info/RECORD,,
347
+ piccolo-0.115.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
348
+ piccolo-0.115.0.dist-info/METADATA,sha256=8DSsJLdSVNh-fxAo23bFxWrtJc9x-cwV9Pmqa9-7afQ,5113
349
+ piccolo-0.115.0.dist-info/WHEEL,sha256=00yskusixUoUt5ob_CiUp6LsnN5lqzTJpoqOFg_FVIc,92
350
+ piccolo-0.115.0.dist-info/entry_points.txt,sha256=zYhu-YNtMlh2N_8wptCS8YWKOgc81UPL3Ji5gly8ouc,47
351
+ piccolo-0.115.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
352
+ piccolo-0.115.0.dist-info/RECORD,,
@@ -1,5 +1,7 @@
1
1
  import datetime
2
2
  import decimal
3
+ import os
4
+ import tempfile
3
5
  import typing as t
4
6
  import uuid
5
7
  from unittest import TestCase
@@ -8,7 +10,7 @@ from piccolo.apps.fixtures.commands.dump import (
8
10
  FixtureConfig,
9
11
  dump_to_json_string,
10
12
  )
11
- from piccolo.apps.fixtures.commands.load import load_json_string
13
+ from piccolo.apps.fixtures.commands.load import load, load_json_string
12
14
  from piccolo.utils.sync import run_sync
13
15
  from tests.base import engines_only
14
16
  from tests.example_apps.mega.tables import MegaTable, SmallTable
@@ -240,3 +242,39 @@ class TestDumpLoad(TestCase):
240
242
  "not_null_col": "hello",
241
243
  },
242
244
  )
245
+
246
+
247
+ class TestOnConflict(TestCase):
248
+ def setUp(self) -> None:
249
+ SmallTable.create_table().run_sync()
250
+ SmallTable({SmallTable.varchar_col: "Test"}).save().run_sync()
251
+
252
+ def tearDown(self) -> None:
253
+ SmallTable.alter().drop_table().run_sync()
254
+
255
+ def test_on_conflict(self):
256
+ temp_dir = tempfile.gettempdir()
257
+
258
+ json_file_path = os.path.join(temp_dir, "fixture.json")
259
+
260
+ json_string = run_sync(
261
+ dump_to_json_string(
262
+ fixture_configs=[
263
+ FixtureConfig(
264
+ app_name="mega",
265
+ table_class_names=["SmallTable"],
266
+ )
267
+ ]
268
+ )
269
+ )
270
+
271
+ if os.path.exists(json_file_path):
272
+ os.unlink(json_file_path)
273
+
274
+ with open(json_file_path, "w") as f:
275
+ f.write(json_string)
276
+
277
+ run_sync(load(path=json_file_path, on_conflict="DO NOTHING"))
278
+ run_sync(load(path=json_file_path, on_conflict="DO UPDATE"))
279
+ run_sync(load(path=json_file_path, on_conflict="do nothing"))
280
+ run_sync(load(path=json_file_path, on_conflict="do update"))
@@ -1138,9 +1138,10 @@ class TestSchemas(MigrationTestCase):
1138
1138
  ).run_sync(),
1139
1139
  )
1140
1140
 
1141
- def test_move_schemas(self):
1141
+ def test_move_table_from_public_schema(self):
1142
1142
  """
1143
- Make sure the auto migrations detect that a table's schema has changed.
1143
+ Make sure the auto migrations can move a table from the public schema
1144
+ to a different schema.
1144
1145
  """
1145
1146
  self._test_migrations(
1146
1147
  table_snapshots=[
@@ -1181,6 +1182,37 @@ class TestSchemas(MigrationTestCase):
1181
1182
  self.schema_manager.list_schemas().run_sync(),
1182
1183
  )
1183
1184
 
1185
+ def test_move_table_to_public_schema(self):
1186
+ """
1187
+ Make sure the auto migrations can move a table from a schema to the
1188
+ public schema.
1189
+ """
1190
+ self._test_migrations(
1191
+ table_snapshots=[
1192
+ [self.manager_2],
1193
+ [self.manager_1],
1194
+ ],
1195
+ )
1196
+
1197
+ # Make sure that the table is in the public schema.
1198
+ self.assertIn(
1199
+ "manager",
1200
+ self.schema_manager.list_tables(schema_name="public").run_sync(),
1201
+ )
1202
+
1203
+ #######################################################################
1204
+
1205
+ # Reverse the last migration, which should move the table back to the
1206
+ # non-public schema.
1207
+ self._run_backwards(migration_id="1")
1208
+
1209
+ self.assertIn(
1210
+ "manager",
1211
+ self.schema_manager.list_tables(
1212
+ schema_name=self.new_schema
1213
+ ).run_sync(),
1214
+ )
1215
+
1184
1216
 
1185
1217
  @engines_only("postgres", "cockroach")
1186
1218
  class TestSameTableName(MigrationTestCase):
File without changes