Skip to content

Commit 0025eca

Browse files
committed
Merge branch 'master' into user_model_enhancements
2 parents ff5d43f + ff45e3e commit 0025eca

File tree

13 files changed

+220
-23
lines changed

13 files changed

+220
-23
lines changed

CHANGES

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
Changes
22
=======
33

4+
0.18.4
5+
------
6+
* Fixed a bug when multiple tables inherit from the same mixin (thanks to
7+
@brnosouza).
8+
* Added a ``log_queries`` option to ``PostgresEngine``, which is useful during
9+
debugging.
10+
* Added the `inflection` library for converting ``Table`` class names to
11+
database table names. Previously, a class called ``TableA`` would wrongly
12+
have a table called ``table`` instead of ``table_a``.
13+
* Fixed a bug with ``SerialisedBuiltin.__hash__`` not returning a number,
14+
which could break migrations (thanks to @sinisaos).
15+
16+
0.18.3
17+
------
18+
Improved ``Array`` column serialisation - needed to fix auto migrations.
19+
420
0.18.2
521
------
622
Added support for filtering ``Array`` columns.

piccolo/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__VERSION__ = "0.18.2"
1+
__VERSION__ = "0.18.4"

piccolo/apps/migrations/auto/migration_manager.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,11 @@ async def _run_add_columns(self, backwards=False):
609609
_Table._meta.tablename = add_columns[0].tablename
610610

611611
for add_column in add_columns:
612-
column = add_column.column
612+
# We fetch the column from the Table, as the metaclass
613+
# copies and sets it up properly.
614+
column = _Table._meta.get_column_by_name(
615+
add_column.column._meta.name
616+
)
613617
await _Table.alter().add_column(
614618
name=column._meta.name, column=column
615619
).run()

piccolo/apps/migrations/auto/serialisation.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import annotations
2+
import builtins
23
from copy import deepcopy
34
from dataclasses import dataclass, field
45
import datetime
@@ -8,6 +9,7 @@
89
import typing as t
910
import uuid
1011

12+
from piccolo.columns import Column
1113
from piccolo.columns.defaults.base import Default
1214
from piccolo.columns.reference import LazyTableReference
1315
from piccolo.table import Table
@@ -17,7 +19,21 @@
1719

1820

1921
@dataclass
20-
class SerialisedClass:
22+
class SerialisedBuiltin:
23+
builtin: t.Any
24+
25+
def __hash__(self):
26+
return hash(self.builtin.__name__)
27+
28+
def __eq__(self, other):
29+
return self.__hash__() == other.__hash__()
30+
31+
def __repr__(self):
32+
return self.builtin.__name__
33+
34+
35+
@dataclass
36+
class SerialisedClassInstance:
2137
instance: object
2238

2339
def __hash__(self):
@@ -26,19 +42,37 @@ def __hash__(self):
2642
def __eq__(self, other):
2743
return self.__hash__() == other.__hash__()
2844

29-
def _serialise_value(self, value):
30-
return f"'{value}'" if isinstance(value, str) else value
31-
3245
def __repr__(self):
3346
args = ", ".join(
3447
[
35-
f"{key}={self._serialise_value(value)}"
48+
f"{key}={value.__repr__()}"
3649
for key, value in self.instance.__dict__.items()
3750
]
3851
)
3952
return f"{self.instance.__class__.__name__}({args})"
4053

4154

55+
@dataclass
56+
class SerialisedColumnInstance:
57+
instance: Column
58+
serialised_params: SerialisedParams
59+
60+
def __hash__(self):
61+
return self.instance.__hash__()
62+
63+
def __eq__(self, other):
64+
return self.__hash__() == other.__hash__()
65+
66+
def __repr__(self):
67+
args = ", ".join(
68+
[
69+
f"{key}={self.serialised_params.params.get(key).__repr__()}" # noqa: E501
70+
for key in self.instance._meta.params.keys()
71+
]
72+
)
73+
return f"{self.instance.__class__.__name__}({args})"
74+
75+
4276
@dataclass
4377
class SerialisedTableType:
4478
table_type: t.Type[Table]
@@ -127,9 +161,40 @@ def serialise_params(params: t.Dict[str, t.Any]) -> SerialisedParams:
127161

128162
for key, value in params.items():
129163

164+
# Builtins, such as str, list and dict.
165+
if (
166+
hasattr(value, "__module__")
167+
and value.__module__ == builtins.__name__
168+
):
169+
params[key] = SerialisedBuiltin(builtin=value)
170+
continue
171+
172+
# Column instances, which are used by Array definitions.
173+
if isinstance(value, Column):
174+
column: Column = value
175+
serialised_params: SerialisedParams = serialise_params(
176+
params=column._meta.params
177+
)
178+
179+
# Include the extra imports and definitions required for the
180+
# column params.
181+
extra_imports.extend(serialised_params.extra_imports)
182+
extra_definitions.extend(serialised_params.extra_definitions)
183+
184+
extra_imports.append(
185+
Import(
186+
module=column.__class__.__module__,
187+
target=column.__class__.__name__,
188+
)
189+
)
190+
params[key] = SerialisedColumnInstance(
191+
instance=value, serialised_params=serialised_params
192+
)
193+
continue
194+
130195
# Class instances
131196
if isinstance(value, Default):
132-
params[key] = SerialisedClass(instance=value)
197+
params[key] = SerialisedClassInstance(instance=value)
133198
extra_imports.append(
134199
Import(
135200
module=value.__class__.__module__,
@@ -227,7 +292,7 @@ def deserialise_params(params: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
227292
if isinstance(value, str) and not isinstance(value, Enum):
228293
if value != "self":
229294
params[key] = deserialise_legacy_params(name=key, value=value)
230-
elif isinstance(value, SerialisedClass):
295+
elif isinstance(value, SerialisedClassInstance):
231296
params[key] = value.instance
232297
elif isinstance(value, SerialisedUUID):
233298
params[key] = value.instance

piccolo/columns/column_types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,10 @@ def __init__(
13841384

13851385
self._validate_default(default, (list, None))
13861386

1387+
# Usually columns are given a name by the Table metaclass, but in this
1388+
# case we have to assign one manually.
1389+
base_column._meta._name = base_column.__class__.__name__
1390+
13871391
self.base_column = base_column
13881392
self.default = default
13891393
self.index: t.Optional[int] = None

piccolo/engine/postgres.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ class PostgresEngine(Engine):
215215
When the engine starts, it will try and create these extensions
216216
in Postgres.
217217
218+
:param log_queries:
219+
If True, all SQL and DDL statements are printed out before being run.
220+
Useful for debugging.
221+
218222
""" # noqa: E501
219223

220224
__slots__ = ("config", "extensions", "pool", "transaction_connection")
@@ -226,9 +230,11 @@ def __init__(
226230
self,
227231
config: t.Dict[str, t.Any],
228232
extensions: t.Sequence[str] = ["uuid-ossp"],
233+
log_queries: bool = False,
229234
) -> None:
230235
self.config = config
231236
self.extensions = extensions
237+
self.log_queries = log_queries
232238
self.pool: t.Optional[Pool] = None
233239
database_name = config.get("database", "Unknown")
234240
self.transaction_connection = contextvars.ContextVar(
@@ -367,6 +373,9 @@ async def run_querystring(
367373
engine_type=self.engine_type
368374
)
369375

376+
if self.log_queries:
377+
print(querystring)
378+
370379
# If running inside a transaction:
371380
connection = self.transaction_connection.get()
372381
if connection:

piccolo/table.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,12 @@ def __init_subclass__(
172172

173173
attribute = getattr(cls, attribute_name)
174174
if isinstance(attribute, Column):
175-
column = attribute
175+
# We have to copy, then override the existing column
176+
# definition, in case this column is inheritted from a mixin.
177+
# Otherwise, when we set attributes on that column, it will
178+
# effect all other users of that mixin.
179+
column = attribute.copy()
180+
setattr(cls, attribute_name, column)
176181

177182
if isinstance(column, PrimaryKey):
178183
# We want it at the start.
@@ -185,8 +190,8 @@ def __init_subclass__(
185190
column._meta._name = attribute_name
186191
column._meta._table = cls
187192

188-
if isinstance(column, ForeignKey):
189-
foreign_key_columns.append(column)
193+
if isinstance(column, ForeignKey):
194+
foreign_key_columns.append(column)
190195

191196
cls._meta = TableMeta(
192197
tablename=tablename,

piccolo/utils/naming.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
import re
1+
import inflection
22

33

4-
_camel_words = re.compile(r"([A-Z][a-z0-9_]+)")
5-
6-
7-
def _camel_to_snake(s):
8-
""" Convert CamelCase to snake_case.
4+
def _camel_to_snake(string: str):
5+
"""
6+
Convert CamelCase to snake_case.
97
"""
10-
return "_".join(
11-
[
12-
i.lower() for i in _camel_words.split(s)[1::2]
13-
]
14-
)
8+
return inflection.underscore(string)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ black
44
colorama>=0.4.0
55
Jinja2>=2.11.0
66
targ>=0.1.0
7+
inflection>=0.5.1

tests/apps/migrations/auto/test_migration_manager.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
from piccolo.columns.column_types import ForeignKey
23
from unittest.mock import patch, MagicMock
34

45
from asyncpg.exceptions import UniqueViolationError
@@ -119,6 +120,7 @@ def test_add_column(self):
119120
table_class_name="Manager",
120121
tablename="manager",
121122
column_name="email",
123+
column_class=Varchar,
122124
column_class_name="Varchar",
123125
params={
124126
"length": 100,
@@ -156,6 +158,7 @@ def test_add_column_with_index(self):
156158
table_class_name="Manager",
157159
tablename="manager",
158160
column_name="email",
161+
column_class=Varchar,
159162
column_class_name="Varchar",
160163
params={
161164
"length": 100,
@@ -187,6 +190,7 @@ def test_add_foreign_key_self_column(self):
187190
table_class_name="Manager",
188191
tablename="manager",
189192
column_name="advisor",
193+
column_class=ForeignKey,
190194
column_class_name="ForeignKey",
191195
params={
192196
"references": "self",
@@ -235,6 +239,7 @@ def test_add_non_nullable_column(self):
235239
table_class_name="Manager",
236240
tablename="manager",
237241
column_name="email",
242+
column_class=Varchar,
238243
column_class_name="Varchar",
239244
params={
240245
"length": 100,

0 commit comments

Comments
 (0)