Skip to content

Commit ab39556

Browse files
authored
Merge pull request piccolo-orm#30 from piccolo-orm/foreign_key_references_as_string
Foreign key references as string
2 parents acd8ebb + 98b15e7 commit ab39556

37 files changed

+753
-142
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ _build/
1414
.coverage
1515
*.sqlite
1616
htmlcov/
17+
prof/

piccolo/apps/migrations/auto/migration_manager.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,15 @@ async def _run_add_columns(self, backwards=False):
511511
AddColumnClass
512512
] = self.add_columns.for_table_class_name(table_class_name)
513513

514+
# Define the table, with the columns, so the metaclass
515+
# sets up the columns correctly.
514516
_Table: t.Type[Table] = type(
515-
add_columns[0].table_class_name, (Table,), {}
517+
add_columns[0].table_class_name,
518+
(Table,),
519+
{
520+
add_column.column._meta.name: add_column.column
521+
for add_column in add_columns
522+
},
516523
)
517524
_Table._meta.tablename = add_columns[0].tablename
518525

piccolo/apps/migrations/auto/serialisation.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import uuid
1010

1111
from piccolo.columns.defaults.base import Default
12+
from piccolo.columns.reference import LazyTableReference
1213
from piccolo.table import Table
1314
from .serialisation_legacy import deserialise_legacy_params
1415

@@ -25,9 +26,15 @@ def __hash__(self):
2526
def __eq__(self, other):
2627
return self.__hash__() == other.__hash__()
2728

29+
def _serialise_value(self, value):
30+
return f"'{value}'" if isinstance(value, str) else value
31+
2832
def __repr__(self):
2933
args = ", ".join(
30-
[f"{key}={value}" for key, value in self.instance.__dict__.items()]
34+
[
35+
f"{key}={self._serialise_value(value)}"
36+
for key, value in self.instance.__dict__.items()
37+
]
3138
)
3239
return f"{self.instance.__class__.__name__}({args})"
3340

@@ -177,6 +184,19 @@ def serialise_params(params: t.Dict[str, t.Any]) -> SerialisedParams:
177184
)
178185
continue
179186

187+
# Lazy imports - we need to resolve these now, in case the target
188+
# table class gets deleted in the future.
189+
if isinstance(value, LazyTableReference):
190+
table_type = value.resolve()
191+
params[key] = SerialisedCallable(callable_=table_type)
192+
extra_definitions.append(
193+
SerialisedTableType(table_type=table_type)
194+
)
195+
extra_imports.append(
196+
Import(module=Table.__module__, target="Table")
197+
)
198+
continue
199+
180200
# Replace any Table class values into class and table names
181201
if inspect.isclass(value) and issubclass(value, Table):
182202
params[key] = SerialisedCallable(callable_=value)

piccolo/apps/sql_shell/commands/run.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from piccolo.engine.sqlite import SQLiteEngine
88
from piccolo.engine.finder import engine_finder
99

10-
if t.TYPE_CHECKING:
10+
if t.TYPE_CHECKING: # pragma: no cover
1111
from piccolo.engine.base import Engine
1212

1313

piccolo/columns/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
UUID,
1818
Varchar,
1919
)
20-
from .base import Column, ForeignKeyMeta, Selectable # noqa: F401
21-
from .base import OnDelete, OnUpdate # noqa: F401
20+
from .base import ( # noqa: F401
21+
Column,
22+
ForeignKeyMeta,
23+
Selectable,
24+
OnDelete,
25+
OnUpdate,
26+
)
2227
from .combination import And, Or, Where # noqa: F401
28+
from .reference import LazyTableReference # noqa: F401

piccolo/columns/base.py

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import datetime
66
import decimal
77
from enum import Enum
8+
import inspect
89
import typing as t
910

1011
from piccolo.columns.operators.comparison import (
@@ -24,12 +25,13 @@
2425
)
2526
from piccolo.columns.combination import Where
2627
from piccolo.columns.defaults.base import Default
28+
from piccolo.columns.reference import LazyTableReference
2729
from piccolo.querystring import QueryString
2830
from piccolo.utils.warnings import colored_warning
2931

30-
if t.TYPE_CHECKING:
31-
from piccolo.table import Table # noqa
32-
from .column_types import ForeignKey # noqa
32+
if t.TYPE_CHECKING: # pragma: no cover
33+
from piccolo.table import Table
34+
from piccolo.columns.column_types import ForeignKey
3335

3436

3537
class OnDelete(str, Enum):
@@ -62,11 +64,47 @@ def __repr__(self):
6264

6365
@dataclass
6466
class ForeignKeyMeta:
65-
references: t.Type[Table]
67+
references: t.Union[t.Type[Table], LazyTableReference]
6668
on_delete: OnDelete
6769
on_update: OnUpdate
6870
proxy_columns: t.List[Column] = field(default_factory=list)
6971

72+
@property
73+
def resolved_references(self) -> t.Type[Table]:
74+
"""
75+
Evaluates the ``references`` attribute if it's a LazyTableReference,
76+
raising a ``ValueError`` if it fails, otherwise returns a ``Table``
77+
subclass.
78+
"""
79+
from piccolo.table import Table
80+
81+
if isinstance(self.references, LazyTableReference):
82+
return self.references.resolve()
83+
elif inspect.isclass(self.references) and issubclass(
84+
self.references, Table
85+
):
86+
return self.references
87+
else:
88+
raise ValueError(
89+
"The references attribute is neither a Table sublclass or a "
90+
"LazyTableReference instance."
91+
)
92+
93+
def copy(self) -> ForeignKeyMeta:
94+
kwargs = self.__dict__.copy()
95+
kwargs.update(proxy_columns=self.proxy_columns.copy())
96+
return self.__class__(**kwargs)
97+
98+
def __copy__(self) -> ForeignKeyMeta:
99+
return self.copy()
100+
101+
def __deepcopy__(self, memo) -> ForeignKeyMeta:
102+
"""
103+
We override deepcopy, as it's too slow if it has to recreate
104+
everything.
105+
"""
106+
return self.copy()
107+
70108

71109
@dataclass
72110
class ColumnMeta:
@@ -141,6 +179,23 @@ def get_full_name(self, just_alias=False) -> str:
141179
else:
142180
return f'{alias} AS "{column_name}"'
143181

182+
def copy(self) -> ColumnMeta:
183+
kwargs = self.__dict__.copy()
184+
kwargs.update(
185+
params=self.params.copy(), call_chain=self.call_chain.copy(),
186+
)
187+
return self.__class__(**kwargs)
188+
189+
def __copy__(self) -> ColumnMeta:
190+
return self.copy()
191+
192+
def __deepcopy__(self, memo) -> ColumnMeta:
193+
"""
194+
We override deepcopy, as it's too slow if it has to recreate
195+
everything.
196+
"""
197+
return self.copy()
198+
144199

145200
class Selectable(metaclass=ABCMeta):
146201
@abstractmethod
@@ -236,7 +291,7 @@ def _validate_default(
236291
"""
237292
Make sure that the default value is of the allowed types.
238293
"""
239-
if getattr(self, "validated", None):
294+
if getattr(self, "_validated", None):
240295
# If it has previously been validated by a subclass, don't
241296
# validate again.
242297
return True
@@ -245,10 +300,10 @@ def _validate_default(
245300
and None in allowed_types
246301
or type(default) in allowed_types
247302
):
248-
self.validated = True
303+
self._validated = True
249304
return True
250305
elif callable(default):
251-
self.validated = True
306+
self._validated = True
252307
return True
253308
else:
254309
raise ValueError(
@@ -409,7 +464,7 @@ def querystring(self) -> QueryString:
409464
self, "_foreign_key_meta", None
410465
)
411466
if foreign_key_meta:
412-
tablename = foreign_key_meta.references._meta.tablename
467+
tablename = foreign_key_meta.resolved_references._meta.tablename
413468
on_delete = foreign_key_meta.on_delete.value
414469
on_update = foreign_key_meta.on_update.value
415470
query += (
@@ -432,8 +487,29 @@ def querystring(self) -> QueryString:
432487

433488
return QueryString(query)
434489

490+
def copy(self) -> Column:
491+
column: Column = copy.copy(self)
492+
column._meta = self._meta.copy()
493+
return column
494+
495+
def __deepcopy__(self, memo) -> Column:
496+
"""
497+
We override deepcopy, as it's too slow if it has to recreate
498+
everything.
499+
"""
500+
return self.copy()
501+
435502
def __str__(self):
436503
return self.querystring.__str__()
437504

438505
def __repr__(self):
439-
return f"{self._meta.name} - {self.__class__.__name__}"
506+
try:
507+
table = self._meta.table
508+
except ValueError:
509+
table_class_name = "Unknown"
510+
else:
511+
table_class_name = table.__name__
512+
return (
513+
f"{table_class_name}.{self._meta.name} - "
514+
f"{self.__class__.__name__}"
515+
)

0 commit comments

Comments
 (0)