Skip to content

Commit 80149d7

Browse files
authored
Merge pull request piccolo-orm#120 from piccolo-orm/fix_protected_tablename_bug
Fix protected tablename bug
2 parents 49562c2 + add7783 commit 80149d7

File tree

6 files changed

+116
-37
lines changed

6 files changed

+116
-37
lines changed

piccolo/apps/migrations/auto/diffable_table.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
serialise_params,
1313
deserialise_params,
1414
)
15-
from piccolo.table import Table
15+
from piccolo.table import Table, create_table_class
1616

1717

1818
def compare_dicts(dict_1, dict_2) -> t.Dict[str, t.Any]:
@@ -144,10 +144,12 @@ def to_table_class(self) -> t.Type[Table]:
144144
"""
145145
Converts the DiffableTable into a Table subclass.
146146
"""
147-
_Table: t.Type[Table] = type(
148-
self.class_name,
149-
(Table,),
150-
{column._meta.name: column for column in self.columns},
147+
_Table: t.Type[Table] = create_table_class(
148+
class_name=self.class_name,
149+
class_kwargs={"tablename": self.tablename},
150+
class_members={
151+
column._meta.name: column for column in self.columns
152+
},
151153
)
152-
_Table._meta.tablename = self.tablename
154+
153155
return _Table

piccolo/apps/migrations/auto/migration_manager.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
RenameTable,
1414
)
1515
from piccolo.apps.migrations.auto.serialisation import deserialise_params
16-
from piccolo.table import Table
16+
from piccolo.table import Table, create_table_class
1717

1818

1919
@dataclass
@@ -329,8 +329,10 @@ async def _run_alter_columns(self, backwards=False):
329329
if not alter_columns:
330330
continue
331331

332-
_Table: t.Type[Table] = type(table_class_name, (Table,), {})
333-
_Table._meta.tablename = alter_columns[0].tablename
332+
_Table: t.Type[Table] = create_table_class(
333+
class_name=table_class_name,
334+
class_kwargs={"tablename": alter_columns[0].tablename},
335+
)
334336

335337
for alter_column in alter_columns:
336338

@@ -492,8 +494,10 @@ async def _run_drop_columns(self, backwards=False):
492494
if not columns:
493495
continue
494496

495-
_Table: t.Type[Table] = type(table_class_name, (Table,), {})
496-
_Table._meta.tablename = columns[0].tablename
497+
_Table: t.Type[Table] = create_table_class(
498+
class_name=table_class_name,
499+
class_kwargs={"tablename": columns[0].tablename},
500+
)
497501

498502
for column in columns:
499503
await _Table.alter().drop_column(
@@ -518,8 +522,9 @@ async def _run_rename_tables(self, backwards=False):
518522
else rename_table.new_tablename
519523
)
520524

521-
_Table: t.Type[Table] = type(class_name, (Table,), {})
522-
_Table._meta.tablename = tablename
525+
_Table: t.Type[Table] = create_table_class(
526+
class_name=class_name, class_kwargs={"tablename": tablename}
527+
)
523528

524529
await _Table.alter().rename_table(new_name=new_tablename).run()
525530

@@ -532,8 +537,10 @@ async def _run_rename_columns(self, backwards=False):
532537
if not columns:
533538
continue
534539

535-
_Table: t.Type[Table] = type(table_class_name, (Table,), {})
536-
_Table._meta.tablename = columns[0].tablename
540+
_Table: t.Type[Table] = create_table_class(
541+
class_name=table_class_name,
542+
class_kwargs={"tablename": columns[0].tablename},
543+
)
537544

538545
for rename_column in columns:
539546
column = (
@@ -559,15 +566,14 @@ async def _run_add_tables(self, backwards=False):
559566
).run()
560567
else:
561568
for add_table in self.add_tables:
562-
_Table: t.Type[Table] = type(
563-
add_table.class_name,
564-
(Table,),
565-
{
569+
_Table: t.Type[Table] = create_table_class(
570+
class_name=add_table.class_name,
571+
class_kwargs={"tablename": add_table.tablename},
572+
class_members={
566573
column._meta.name: column
567574
for column in add_table.columns
568575
},
569576
)
570-
_Table._meta.tablename = add_table.tablename
571577

572578
await _Table.create_table().run()
573579

@@ -584,10 +590,10 @@ async def _run_add_columns(self, backwards=False):
584590
# be deleted.
585591
continue
586592

587-
_Table: t.Type[Table] = type(
588-
add_column.table_class_name, (Table,), {}
593+
_Table: t.Type[Table] = create_table_class(
594+
class_name=add_column.table_class_name,
595+
class_kwargs={"tablename": add_column.tablename},
589596
)
590-
_Table._meta.tablename = add_column.tablename
591597

592598
await _Table.alter().drop_column(add_column.column).run()
593599
else:
@@ -598,15 +604,14 @@ async def _run_add_columns(self, backwards=False):
598604

599605
# Define the table, with the columns, so the metaclass
600606
# sets up the columns correctly.
601-
_Table: t.Type[Table] = type(
602-
add_columns[0].table_class_name,
603-
(Table,),
604-
{
607+
_Table: t.Type[Table] = create_table_class(
608+
class_name=add_columns[0].table_class_name,
609+
class_kwargs={"tablename": add_columns[0].tablename},
610+
class_members={
605611
add_column.column._meta.name: add_column.column
606612
for add_column in add_columns
607613
},
608614
)
609-
_Table._meta.tablename = add_columns[0].tablename
610615

611616
for add_column in add_columns:
612617
# We fetch the column from the Table, as the metaclass

piccolo/apps/migrations/auto/serialisation_legacy.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import typing as t
44

55
from piccolo.columns.column_types import OnDelete, OnUpdate
6-
from piccolo.table import Table
6+
from piccolo.table import Table, create_table_class
77
from piccolo.columns.defaults.timestamp import TimestampNow
88

99

@@ -25,11 +25,10 @@ def deserialise_legacy_params(name: str, value: str) -> t.Any:
2525
"`SomeClassName` or `SomeClassName|some_table_name`."
2626
)
2727

28-
_Table: t.Type[Table] = type(
29-
class_name, (Table,), {},
28+
_Table: t.Type[Table] = create_table_class(
29+
class_name=class_name,
30+
class_kwargs={"tablename": tablename} if tablename else {},
3031
)
31-
if tablename:
32-
_Table._meta.tablename = tablename
3332
return _Table
3433

3534
###########################################################################

piccolo/table.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from enum import Enum
44
import inspect
55
import itertools
6+
import types
67
import typing as t
78

89
from piccolo.engine import Engine, engine_finder
@@ -719,3 +720,32 @@ def _table_str(cls, abbreviated=False):
719720
return (
720721
f"class {cls.__name__}({class_args}):\n" f" {columns_string}\n"
721722
)
723+
724+
725+
def create_table_class(
726+
class_name: str,
727+
bases: t.Tuple[t.Type] = (Table,),
728+
class_kwargs: t.Dict[str, t.Any] = {},
729+
class_members: t.Dict[str, t.Any] = {},
730+
) -> t.Type[Table]:
731+
"""
732+
Used to dynamically create ``Table``subclasses at runtime. Most users
733+
will not require this. It's mostly used internally for Piccolo's
734+
migrations.
735+
736+
:param class_name:
737+
For example `'MyTable'`.
738+
:param bases:
739+
A tuple of parent classes - usually just `(Table,)`.
740+
:param class_kwargs:
741+
For example, `{'tablename': 'my_custom_tablename'}`.
742+
:param class_members:
743+
For example, `{'my_column': Varchar()}`.
744+
745+
"""
746+
return types.new_class(
747+
name=class_name,
748+
bases=bases,
749+
kwds=class_kwargs,
750+
exec_body=lambda namespace: namespace.update(class_members),
751+
)

tests/base.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from piccolo.engine.finder import engine_finder
99
from piccolo.engine.postgres import PostgresEngine
1010
from piccolo.engine.sqlite import SQLiteEngine
11-
from piccolo.table import Table
11+
from piccolo.table import Table, create_table_class
1212

1313

1414
ENGINE = engine_finder()
@@ -47,12 +47,13 @@ class DBTestCase(TestCase):
4747
"""
4848

4949
def run_sync(self, query):
50-
_Table = type("_Table", (Table,), {})
50+
_Table = create_table_class(class_name="_Table")
5151
return _Table.raw(query).run_sync()
5252

5353
def table_exists(self, tablename: str) -> bool:
54-
_Table: t.Type[Table] = type(tablename.upper(), (Table,), {})
55-
_Table._meta.tablename = tablename
54+
_Table: t.Type[Table] = create_table_class(
55+
class_name=tablename.upper(), class_kwargs={"tablename": tablename}
56+
)
5657
return _Table.table_exists().run_sync()
5758

5859
###########################################################################
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from unittest import TestCase
2+
3+
from piccolo.table import create_table_class
4+
from piccolo.columns import Varchar
5+
6+
7+
class TestCreateTableClass(TestCase):
8+
def test_create_table_class(self):
9+
"""
10+
Make sure a basic `Table` can be created successfully.
11+
"""
12+
_Table = create_table_class(class_name="MyTable")
13+
self.assertEqual(_Table._meta.tablename, "my_table")
14+
15+
_Table = create_table_class(
16+
class_name="MyTable", class_kwargs={"tablename": "my_table_1"}
17+
)
18+
self.assertEqual(_Table._meta.tablename, "my_table_1")
19+
20+
column = Varchar()
21+
_Table = create_table_class(
22+
class_name="MyTable", class_members={"name": column}
23+
)
24+
self.assertTrue(column in _Table._meta.columns)
25+
26+
def test_protected_tablenames(self):
27+
"""
28+
Make sure that the logic around protected tablenames still works as
29+
expected.
30+
"""
31+
with self.assertRaises(ValueError):
32+
create_table_class(class_name="User")
33+
34+
with self.assertRaises(ValueError):
35+
create_table_class(
36+
class_name="MyUser", class_kwargs={"tablename": "user"}
37+
)
38+
39+
# This shouldn't raise an error:
40+
create_table_class(
41+
class_name="User", class_kwargs={"tablename": "my_user"}
42+
)

0 commit comments

Comments
 (0)