Skip to content

Commit 72a605d

Browse files
committed
Refactor serialize source to use new base class
1 parent 1633c11 commit 72a605d

2 files changed

Lines changed: 20 additions & 54 deletions

File tree

morango/sync/stream/serialize.py

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from typing import Type
99

1010
from django.core.serializers.json import DjangoJSONEncoder
11-
from django.db.models import Q
1211

1312
from morango.models.certificates import Filter
1413
from morango.models.core import DatabaseMaxCounter
@@ -21,15 +20,16 @@
2120
from morango.registry import syncable_models
2221
from morango.sync.stream.core import Buffer
2322
from morango.sync.stream.core import Sink
24-
from morango.sync.stream.core import Source
2523
from morango.sync.stream.core import Transform
2624
from morango.sync.stream.core import Unbuffer
25+
from morango.sync.stream.source import MorangoSource
26+
from morango.sync.stream.source import SourceTask
2727
from morango.utils import self_referential_fk
2828

2929
logger = logging.getLogger(__name__)
3030

3131

32-
class SerializeTask(object):
32+
class SerializeTask(SourceTask):
3333
"""Carrier class for providing context through the pipeline"""
3434

3535
__slots__ = ("model", "obj", "store", "counter")
@@ -40,6 +40,10 @@ def __init__(self, model: Type[SyncableModel], obj: SyncableModel):
4040
self.store: Optional[Store] = None
4141
self.counter: Optional[RecordMaxCounter] = None
4242

43+
@property
44+
def id(self) -> str:
45+
return self.obj.id
46+
4347
@property
4448
def is_store_update(self):
4549
return self.store is not None and not self.store._state.adding
@@ -59,59 +63,22 @@ def self_referential_fk(self) -> Optional[str]:
5963
return self_referential_fk(self.model)
6064

6165

62-
class AppModelSource(Source[SerializeTask]):
66+
class AppModelSource(MorangoSource[SerializeTask]):
6367
"""
6468
Yields ``SerializeTask`` objects for every syncable-model record that matches the
6569
optional *sync_filter*.
6670
"""
6771

68-
def __init__(
69-
self,
70-
profile: str,
71-
sync_filter: Optional[Filter] = None,
72-
dirty_only: bool = True,
73-
partition_order: str = "asc",
74-
):
75-
"""
76-
:param profile: The Morango model profile
77-
:param sync_filter: The Filter object for this sync
78-
:param dirty_only: Whether to filter on dirty records only
79-
:param partition_order: Controls how the filter specificity is applied, "asc" or "desc"
80-
"""
81-
self.profile = profile
82-
self.sync_filter = sync_filter
83-
self.dirty_only = dirty_only
84-
self.partition_order = partition_order
85-
self._seen = set()
86-
87-
def prefix_conditions(self) -> Generator[Optional[Q], None, None]:
88-
if self.sync_filter is None:
89-
# yield None once, so we do one query without a partition filter (everything)
90-
yield None
91-
else:
92-
partitions_prefixes = [str(prefix) for prefix in self.sync_filter]
93-
partition_iterator = sorted(
94-
partitions_prefixes,
95-
reverse=self.partition_order == "desc",
96-
)
97-
98-
for prefix in partition_iterator:
99-
yield Q(_morango_partition__startswith=prefix)
100-
101-
def stream(self) -> Generator[SerializeTask, None, None]:
102-
for partition_condition in self.prefix_conditions():
103-
for qs in syncable_models.get_model_querysets(self.profile):
104-
if partition_condition is not None:
105-
qs = qs.filter(partition_condition)
106-
if self.dirty_only:
107-
qs = qs.filter(_morango_dirty_bit=True)
108-
for obj in qs.iterator():
109-
# partition filtering could result in overlaps, and since we're walking
110-
# through the partitions one by one, we should avoid duplicates. Morango
111-
# syncable models have unique IDs across the entire profile
112-
if obj.id not in self._seen:
113-
self._seen.add(obj.id)
114-
yield SerializeTask(qs.model, obj)
72+
def stream_for_filter(
73+
self, partition_condition: Optional[str]
74+
) -> Generator[SerializeTask, None, None]:
75+
for qs in syncable_models.get_model_querysets(self.profile):
76+
if partition_condition is not None:
77+
qs = qs.filter(_morango_partition__startswith=partition_condition)
78+
if self.dirty_only:
79+
qs = qs.filter(_morango_dirty_bit=True)
80+
for obj in qs.iterator():
81+
yield SerializeTask(qs.model, obj)
11582

11683

11784
class StoreLookup(Transform[List[SerializeTask]]):

tests/testapp/tests/sync/stream/test_serialize.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import json
22

33
import mock
4-
from django.db.models import Q
54
from django.test import SimpleTestCase
65

76
from morango.models.certificates import Filter
@@ -59,7 +58,7 @@ def test_prefix_conditions__with_filter(self):
5958
conditions = list(source.prefix_conditions())
6059
self.assertEqual(len(conditions), 2)
6160
self.assertEqual(
62-
str(conditions[0]), "(AND: ('_morango_partition__startswith', 'a'))"
61+
str(conditions[0]), "a"
6362
)
6463

6564
@mock.patch("morango.sync.stream.serialize.syncable_models.get_model_querysets")
@@ -116,7 +115,7 @@ def test_stream__partition(self, mock_get_model_querysets):
116115
self.assertEqual(tasks[0].model, model)
117116
self.assertEqual(tasks[0].obj, obj)
118117
mock_get_model_querysets.assert_called_once_with("test")
119-
qs.filter.assert_called_once_with(Q(_morango_partition__startswith="a"))
118+
qs.filter.assert_called_once_with(_morango_partition__startswith="a")
120119

121120

122121
class StoreLookupTestCase(SimpleTestCase):

0 commit comments

Comments
 (0)