piccolo 1.5.2__py3-none-any.whl → 1.7.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__ = "1.5.2"
1
+ __VERSION__ = "1.7.0"
piccolo/columns/base.py CHANGED
@@ -6,7 +6,6 @@ import decimal
6
6
  import inspect
7
7
  import typing as t
8
8
  import uuid
9
- from abc import ABCMeta, abstractmethod
10
9
  from dataclasses import dataclass, field, fields
11
10
  from enum import Enum
12
11
 
@@ -32,6 +31,7 @@ from piccolo.columns.operators.comparison import (
32
31
  NotLike,
33
32
  )
34
33
  from piccolo.columns.reference import LazyTableReference
34
+ from piccolo.querystring import QueryString, Selectable
35
35
  from piccolo.utils.warnings import colored_warning
36
36
 
37
37
  if t.TYPE_CHECKING: # pragma: no cover
@@ -205,7 +205,6 @@ class ColumnMeta:
205
205
 
206
206
  # Used by Foreign Keys:
207
207
  call_chain: t.List["ForeignKey"] = field(default_factory=list)
208
- table_alias: t.Optional[str] = None
209
208
 
210
209
  ###########################################################################
211
210
 
@@ -260,7 +259,7 @@ class ColumnMeta:
260
259
  column_name = self.db_column_name
261
260
 
262
261
  if self.call_chain:
263
- table_alias = self.call_chain[-1]._meta.table_alias
262
+ table_alias = self.call_chain[-1].table_alias
264
263
  if include_quotes:
265
264
  return f'"{table_alias}"."{column_name}"'
266
265
  else:
@@ -272,7 +271,9 @@ class ColumnMeta:
272
271
  return f"{self.table._meta.tablename}.{column_name}"
273
272
 
274
273
  def get_full_name(
275
- self, with_alias: bool = True, include_quotes: bool = True
274
+ self,
275
+ with_alias: bool = True,
276
+ include_quotes: bool = True,
276
277
  ) -> str:
277
278
  """
278
279
  Returns the full column name, taking into account joins.
@@ -302,11 +303,10 @@ class ColumnMeta:
302
303
  >>> column._meta.get_full_name(include_quotes=False)
303
304
  'my_table_name.my_column_name'
304
305
 
305
-
306
306
  """
307
307
  full_name = self._get_path(include_quotes=include_quotes)
308
308
 
309
- if with_alias and self.call_chain:
309
+ if with_alias:
310
310
  alias = self.get_default_alias()
311
311
  if include_quotes:
312
312
  full_name += f' AS "{alias}"'
@@ -346,32 +346,6 @@ class ColumnMeta:
346
346
  return self.copy()
347
347
 
348
348
 
349
- class Selectable(metaclass=ABCMeta):
350
- """
351
- Anything which inherits from this can be used in a select query.
352
- """
353
-
354
- _alias: t.Optional[str]
355
-
356
- @abstractmethod
357
- def get_select_string(
358
- self, engine_type: str, with_alias: bool = True
359
- ) -> str:
360
- """
361
- In a query, what to output after the select statement - could be a
362
- column name, a sub query, a function etc. For a column it will be the
363
- column name.
364
- """
365
- raise NotImplementedError()
366
-
367
- def as_alias(self, alias: str) -> Selectable:
368
- """
369
- Allows column names to be changed in the result of a select.
370
- """
371
- self._alias = alias
372
- return self
373
-
374
-
375
349
  class Column(Selectable):
376
350
  """
377
351
  All other columns inherit from ``Column``. Don't use it directly.
@@ -822,25 +796,32 @@ class Column(Selectable):
822
796
 
823
797
  def get_select_string(
824
798
  self, engine_type: str, with_alias: bool = True
825
- ) -> str:
799
+ ) -> QueryString:
826
800
  """
827
801
  How to refer to this column in a SQL query, taking account of any joins
828
802
  and aliases.
829
803
  """
804
+
830
805
  if with_alias:
831
806
  if self._alias:
832
807
  original_name = self._meta.get_full_name(
833
808
  with_alias=False,
834
809
  )
835
- return f'{original_name} AS "{self._alias}"'
810
+ return QueryString(f'{original_name} AS "{self._alias}"')
836
811
  else:
837
- return self._meta.get_full_name(
838
- with_alias=True,
812
+ return QueryString(
813
+ self._meta.get_full_name(
814
+ with_alias=True,
815
+ )
839
816
  )
840
817
 
841
- return self._meta.get_full_name(with_alias=False)
818
+ return QueryString(
819
+ self._meta.get_full_name(
820
+ with_alias=False,
821
+ )
822
+ )
842
823
 
843
- def get_where_string(self, engine_type: str) -> str:
824
+ def get_where_string(self, engine_type: str) -> QueryString:
844
825
  return self.get_select_string(
845
826
  engine_type=engine_type, with_alias=False
846
827
  )
@@ -902,6 +883,13 @@ class Column(Selectable):
902
883
  def column_type(self):
903
884
  return self.__class__.__name__.upper()
904
885
 
886
+ @property
887
+ def table_alias(self) -> str:
888
+ return "$".join(
889
+ f"{_key._meta.table._meta.tablename}${_key._meta.name}"
890
+ for _key in [*self._meta.call_chain, self]
891
+ )
892
+
905
893
  @property
906
894
  def ddl(self) -> str:
907
895
  """
@@ -945,8 +933,8 @@ class Column(Selectable):
945
933
 
946
934
  return query
947
935
 
948
- def copy(self) -> Column:
949
- column: Column = copy.copy(self)
936
+ def copy(self: Self) -> Self:
937
+ column = copy.copy(self)
950
938
  column._meta = self._meta.copy()
951
939
  return column
952
940
 
@@ -971,3 +959,6 @@ class Column(Selectable):
971
959
  f"{table_class_name}.{self._meta.name} - "
972
960
  f"{self.__class__.__name__}"
973
961
  )
962
+
963
+
964
+ Self = t.TypeVar("Self", bound=Column)
@@ -60,7 +60,7 @@ from piccolo.columns.defaults.uuid import UUID4, UUIDArg
60
60
  from piccolo.columns.operators.comparison import ArrayAll, ArrayAny
61
61
  from piccolo.columns.operators.string import Concat
62
62
  from piccolo.columns.reference import LazyTableReference
63
- from piccolo.querystring import QueryString, Unquoted
63
+ from piccolo.querystring import QueryString
64
64
  from piccolo.utils.encoding import dump_json
65
65
  from piccolo.utils.warnings import colored_warning
66
66
 
@@ -752,8 +752,8 @@ class SmallInt(Integer):
752
752
  ###############################################################################
753
753
 
754
754
 
755
- DEFAULT = Unquoted("DEFAULT")
756
- NULL = Unquoted("null")
755
+ DEFAULT = QueryString("DEFAULT")
756
+ NULL = QueryString("null")
757
757
 
758
758
 
759
759
  class Serial(Column):
@@ -778,7 +778,7 @@ class Serial(Column):
778
778
  if engine_type == "postgres":
779
779
  return DEFAULT
780
780
  elif engine_type == "cockroach":
781
- return Unquoted("unique_rowid()")
781
+ return QueryString("unique_rowid()")
782
782
  elif engine_type == "sqlite":
783
783
  return NULL
784
784
  raise Exception("Unrecognized engine type")
@@ -2194,6 +2194,7 @@ class ForeignKey(Column, t.Generic[ReferencedTable]):
2194
2194
  column_meta: ColumnMeta = object.__getattribute__(self, "_meta")
2195
2195
 
2196
2196
  new_column._meta.call_chain = column_meta.call_chain.copy()
2197
+
2197
2198
  new_column._meta.call_chain.append(self)
2198
2199
  return new_column
2199
2200
  else:
@@ -2311,7 +2312,7 @@ class JSONB(JSON):
2311
2312
 
2312
2313
  def get_select_string(
2313
2314
  self, engine_type: str, with_alias: bool = True
2314
- ) -> str:
2315
+ ) -> QueryString:
2315
2316
  select_string = self._meta.get_full_name(with_alias=False)
2316
2317
 
2317
2318
  if self.json_operator is not None:
@@ -2321,7 +2322,7 @@ class JSONB(JSON):
2321
2322
  alias = self._alias or self._meta.get_default_alias()
2322
2323
  select_string += f' AS "{alias}"'
2323
2324
 
2324
- return select_string
2325
+ return QueryString(select_string)
2325
2326
 
2326
2327
  def eq(self, value) -> Where:
2327
2328
  """
@@ -2531,7 +2532,14 @@ class Array(Column):
2531
2532
  if engine_type in ("postgres", "cockroach"):
2532
2533
  return f"{self.base_column.column_type}[]"
2533
2534
  elif engine_type == "sqlite":
2534
- return "ARRAY"
2535
+ inner_column = self._get_inner_column()
2536
+ return (
2537
+ f"ARRAY_{inner_column.column_type}"
2538
+ if isinstance(
2539
+ inner_column, (Date, Timestamp, Timestamptz, Time)
2540
+ )
2541
+ else "ARRAY"
2542
+ )
2535
2543
  raise Exception("Unrecognized engine type")
2536
2544
 
2537
2545
  def _setup_base_column(self, table_class: t.Type[Table]):
@@ -2563,6 +2571,23 @@ class Array(Column):
2563
2571
  else:
2564
2572
  return start + 1
2565
2573
 
2574
+ def _get_inner_column(self) -> Column:
2575
+ """
2576
+ A helper function to get the innermost ``Column`` for the array. For
2577
+ example::
2578
+
2579
+ >>> Array(Varchar())._get_inner_column()
2580
+ Varchar
2581
+
2582
+ >>> Array(Array(Varchar()))._get_inner_column()
2583
+ Varchar
2584
+
2585
+ """
2586
+ if isinstance(self.base_column, Array):
2587
+ return self.base_column._get_inner_column()
2588
+ else:
2589
+ return self.base_column
2590
+
2566
2591
  def _get_inner_value_type(self) -> t.Type:
2567
2592
  """
2568
2593
  A helper function to get the innermost value type for the array. For
@@ -2575,10 +2600,7 @@ class Array(Column):
2575
2600
  str
2576
2601
 
2577
2602
  """
2578
- if isinstance(self.base_column, Array):
2579
- return self.base_column._get_inner_value_type()
2580
- else:
2581
- return self.base_column.value_type
2603
+ return self._get_inner_column().value_type
2582
2604
 
2583
2605
  def __getitem__(self, value: int) -> Array:
2584
2606
  """
@@ -2616,7 +2638,9 @@ class Array(Column):
2616
2638
  else:
2617
2639
  raise ValueError("Only integers can be used for indexing.")
2618
2640
 
2619
- def get_select_string(self, engine_type: str, with_alias=True) -> str:
2641
+ def get_select_string(
2642
+ self, engine_type: str, with_alias=True
2643
+ ) -> QueryString:
2620
2644
  select_string = self._meta.get_full_name(with_alias=False)
2621
2645
 
2622
2646
  if isinstance(self.index, int):
@@ -2626,7 +2650,7 @@ class Array(Column):
2626
2650
  alias = self._alias or self._meta.get_default_alias()
2627
2651
  select_string += f' AS "{alias}"'
2628
2652
 
2629
- return select_string
2653
+ return QueryString(select_string)
2630
2654
 
2631
2655
  def any(self, value: t.Any) -> Where:
2632
2656
  """
piccolo/columns/m2m.py CHANGED
@@ -4,7 +4,6 @@ import inspect
4
4
  import typing as t
5
5
  from dataclasses import dataclass
6
6
 
7
- from piccolo.columns.base import Selectable
8
7
  from piccolo.columns.column_types import (
9
8
  JSON,
10
9
  JSONB,
@@ -12,6 +11,7 @@ from piccolo.columns.column_types import (
12
11
  ForeignKey,
13
12
  LazyTableReference,
14
13
  )
14
+ from piccolo.querystring import QueryString, Selectable
15
15
  from piccolo.utils.list import flatten
16
16
  from piccolo.utils.sync import run_sync
17
17
 
@@ -56,7 +56,9 @@ class M2MSelect(Selectable):
56
56
  for column in columns
57
57
  )
58
58
 
59
- def get_select_string(self, engine_type: str, with_alias=True) -> str:
59
+ def get_select_string(
60
+ self, engine_type: str, with_alias=True
61
+ ) -> QueryString:
60
62
  m2m_table_name_with_schema = (
61
63
  self.m2m._meta.resolved_joining_table._meta.get_formatted_tablename() # noqa: E501
62
64
  ) # noqa: E501
@@ -90,28 +92,33 @@ class M2MSelect(Selectable):
90
92
  if engine_type in ("postgres", "cockroach"):
91
93
  if self.as_list:
92
94
  column_name = self.columns[0]._meta.db_column_name
93
- return f"""
95
+ return QueryString(
96
+ f"""
94
97
  ARRAY(
95
98
  SELECT
96
99
  "inner_{table_2_name}"."{column_name}"
97
100
  FROM {inner_select}
98
101
  ) AS "{m2m_relationship_name}"
99
102
  """
103
+ )
100
104
  elif not self.serialisation_safe:
101
105
  column_name = table_2_pk_name
102
- return f"""
106
+ return QueryString(
107
+ f"""
103
108
  ARRAY(
104
109
  SELECT
105
110
  "inner_{table_2_name}"."{column_name}"
106
111
  FROM {inner_select}
107
112
  ) AS "{m2m_relationship_name}"
108
113
  """
114
+ )
109
115
  else:
110
116
  column_names = ", ".join(
111
117
  f'"inner_{table_2_name}"."{column._meta.db_column_name}"'
112
118
  for column in self.columns
113
119
  )
114
- return f"""
120
+ return QueryString(
121
+ f"""
115
122
  (
116
123
  SELECT JSON_AGG({m2m_relationship_name}_results)
117
124
  FROM (
@@ -119,13 +126,15 @@ class M2MSelect(Selectable):
119
126
  ) AS "{m2m_relationship_name}_results"
120
127
  ) AS "{m2m_relationship_name}"
121
128
  """
129
+ )
122
130
  elif engine_type == "sqlite":
123
131
  if len(self.columns) > 1 or not self.serialisation_safe:
124
132
  column_name = table_2_pk_name
125
133
  else:
126
134
  column_name = self.columns[0]._meta.db_column_name
127
135
 
128
- return f"""
136
+ return QueryString(
137
+ f"""
129
138
  (
130
139
  SELECT group_concat(
131
140
  "inner_{table_2_name}"."{column_name}"
@@ -134,6 +143,7 @@ class M2MSelect(Selectable):
134
143
  )
135
144
  AS "{m2m_relationship_name} [M2M]"
136
145
  """
146
+ )
137
147
  else:
138
148
  raise ValueError(f"{engine_type} is an unrecognised engine type")
139
149
 
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import typing as t
4
4
  from dataclasses import dataclass
5
5
 
6
- from piccolo.columns.base import Selectable
6
+ from piccolo.querystring import QueryString, Selectable
7
7
 
8
8
  if t.TYPE_CHECKING: # pragma: no cover
9
9
  from piccolo.columns.base import Column
@@ -27,25 +27,27 @@ class Readable(Selectable):
27
27
  i._meta.get_full_name(with_alias=False) for i in self.columns
28
28
  )
29
29
 
30
- def _get_string(self, operator: str) -> str:
31
- return (
30
+ def _get_string(self, operator: str) -> QueryString:
31
+ return QueryString(
32
32
  f"{operator}('{self.template}', {self._columns_string}) AS "
33
33
  f"{self.output_name}"
34
34
  )
35
35
 
36
36
  @property
37
- def sqlite_string(self) -> str:
37
+ def sqlite_string(self) -> QueryString:
38
38
  return self._get_string(operator="PRINTF")
39
39
 
40
40
  @property
41
- def postgres_string(self) -> str:
41
+ def postgres_string(self) -> QueryString:
42
42
  return self._get_string(operator="FORMAT")
43
43
 
44
44
  @property
45
- def cockroach_string(self) -> str:
45
+ def cockroach_string(self) -> QueryString:
46
46
  return self._get_string(operator="FORMAT")
47
47
 
48
- def get_select_string(self, engine_type: str, with_alias=True) -> str:
48
+ def get_select_string(
49
+ self, engine_type: str, with_alias=True
50
+ ) -> QueryString:
49
51
  try:
50
52
  return getattr(self, f"{engine_type}_string")
51
53
  except AttributeError as e: