morango 0.6.11__py2.py3-none-any.whl → 0.8.6__py2.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.
- morango/__init__.py +1 -6
- morango/api/serializers.py +3 -0
- morango/api/viewsets.py +38 -23
- morango/apps.py +1 -2
- morango/constants/settings.py +3 -0
- morango/constants/transfer_stages.py +1 -1
- morango/constants/transfer_statuses.py +1 -1
- morango/errors.py +4 -0
- morango/management/commands/cleanupsyncs.py +64 -14
- morango/migrations/0001_initial.py +0 -2
- morango/migrations/0001_squashed_0024_auto_20240129_1757.py +583 -0
- morango/migrations/0002_auto_20170511_0400.py +0 -2
- morango/migrations/0002_store_idx_morango_deserialize.py +21 -0
- morango/migrations/0003_auto_20170519_0543.py +0 -2
- morango/migrations/0004_auto_20170520_2112.py +0 -2
- morango/migrations/0005_auto_20170629_2139.py +0 -2
- morango/migrations/0006_instanceidmodel_system_id.py +0 -2
- morango/migrations/0007_auto_20171018_1615.py +0 -2
- morango/migrations/0008_auto_20171114_2217.py +0 -2
- morango/migrations/0009_auto_20171205_0252.py +0 -2
- morango/migrations/0010_auto_20171206_1615.py +0 -2
- morango/migrations/0011_sharedkey.py +0 -2
- morango/migrations/0012_auto_20180927_1658.py +0 -2
- morango/migrations/0013_auto_20190627_1513.py +0 -2
- morango/migrations/0014_syncsession_extra_fields.py +0 -2
- morango/migrations/0015_auto_20200508_2104.py +2 -3
- morango/migrations/0016_store_deserialization_error.py +2 -3
- morango/migrations/0017_store_last_transfer_session_id.py +1 -2
- morango/migrations/0018_auto_20210714_2216.py +2 -3
- morango/migrations/0019_auto_20220113_1807.py +2 -3
- morango/migrations/0020_postgres_fix_nullable.py +0 -2
- morango/migrations/0021_store_partition_index_create.py +0 -2
- morango/migrations/0022_rename_instance_fields.py +23 -0
- morango/migrations/0023_add_instance_id_fields.py +24 -0
- morango/migrations/0024_auto_20240129_1757.py +28 -0
- morango/models/__init__.py +0 -6
- morango/models/certificates.py +137 -28
- morango/models/core.py +48 -46
- morango/models/fields/crypto.py +20 -6
- morango/models/fields/uuids.py +2 -1
- morango/models/utils.py +5 -6
- morango/proquint.py +2 -3
- morango/registry.py +28 -49
- morango/sync/backends/base.py +34 -0
- morango/sync/backends/postgres.py +129 -0
- morango/sync/backends/utils.py +10 -11
- morango/sync/context.py +198 -13
- morango/sync/controller.py +33 -11
- morango/sync/operations.py +324 -251
- morango/sync/session.py +11 -0
- morango/sync/syncsession.py +78 -85
- morango/sync/utils.py +18 -0
- morango/urls.py +3 -3
- morango/utils.py +2 -3
- {morango-0.6.11.dist-info → morango-0.8.6.dist-info}/METADATA +29 -14
- morango-0.8.6.dist-info/RECORD +79 -0
- {morango-0.6.11.dist-info → morango-0.8.6.dist-info}/WHEEL +1 -1
- morango/models/morango_mptt.py +0 -33
- morango/settings.py +0 -115
- morango/wsgi.py +0 -33
- morango-0.6.11.dist-info/RECORD +0 -77
- {morango-0.6.11.dist-info → morango-0.8.6.dist-info/licenses}/AUTHORS.md +0 -0
- {morango-0.6.11.dist-info → morango-0.8.6.dist-info/licenses}/LICENSE +0 -0
- {morango-0.6.11.dist-info → morango-0.8.6.dist-info}/top_level.txt +0 -0
morango/sync/session.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
from requests import exceptions
|
|
4
|
+
from morango import __version__
|
|
4
5
|
from requests.sessions import Session
|
|
5
6
|
from requests.utils import super_len
|
|
6
7
|
from requests.packages.urllib3.util.url import parse_url
|
|
7
8
|
|
|
8
9
|
from morango.utils import serialize_capabilities_to_client_request
|
|
10
|
+
from morango.utils import SETTINGS
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
@@ -35,6 +37,15 @@ class SessionWrapper(Session):
|
|
|
35
37
|
bytes_sent = 0
|
|
36
38
|
bytes_received = 0
|
|
37
39
|
|
|
40
|
+
def __init__(self):
|
|
41
|
+
super(SessionWrapper, self).__init__()
|
|
42
|
+
user_agent_header = "morango/{}".format(__version__)
|
|
43
|
+
if SETTINGS.CUSTOM_INSTANCE_INFO is not None:
|
|
44
|
+
instances = list(SETTINGS.CUSTOM_INSTANCE_INFO)
|
|
45
|
+
if instances:
|
|
46
|
+
user_agent_header += " " + "{}/{}".format(instances[0], SETTINGS.CUSTOM_INSTANCE_INFO.get(instances[0]))
|
|
47
|
+
self.headers["User-Agent"] = "{} {}".format(user_agent_header, self.headers["User-Agent"])
|
|
48
|
+
|
|
38
49
|
def request(self, method, url, **kwargs):
|
|
39
50
|
response = None
|
|
40
51
|
try:
|
morango/sync/syncsession.py
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The main module to be used for initiating the synchronization of data between morango instances.
|
|
3
3
|
"""
|
|
4
|
-
import os
|
|
5
4
|
import json
|
|
6
5
|
import logging
|
|
6
|
+
import os
|
|
7
7
|
import socket
|
|
8
8
|
import uuid
|
|
9
9
|
from io import BytesIO
|
|
10
|
+
from urllib.parse import urljoin
|
|
11
|
+
from urllib.parse import urlparse
|
|
10
12
|
|
|
11
13
|
from django.utils import timezone
|
|
12
|
-
from django.utils.six import iteritems
|
|
13
|
-
from django.utils.six import raise_from
|
|
14
14
|
from requests.adapters import HTTPAdapter
|
|
15
15
|
from requests.exceptions import HTTPError
|
|
16
16
|
from requests.packages.urllib3.util.retry import Retry
|
|
17
|
-
from django.
|
|
18
|
-
from django.utils.six.moves.urllib.parse import urlparse
|
|
17
|
+
from django.db import transaction, connection
|
|
19
18
|
|
|
20
19
|
from .session import SessionWrapper
|
|
21
20
|
from morango.api.serializers import CertificateSerializer
|
|
@@ -30,16 +29,20 @@ from morango.errors import MorangoError
|
|
|
30
29
|
from morango.errors import MorangoResumeSyncError
|
|
31
30
|
from morango.errors import MorangoServerDoesNotAllowNewCertPush
|
|
32
31
|
from morango.models.certificates import Certificate
|
|
32
|
+
from morango.models.certificates import Filter
|
|
33
33
|
from morango.models.certificates import Key
|
|
34
34
|
from morango.models.core import InstanceIDModel
|
|
35
35
|
from morango.models.core import SyncSession
|
|
36
|
-
from morango.sync.
|
|
36
|
+
from morango.sync.backends.utils import load_backend
|
|
37
|
+
from morango.sync.context import CompositeSessionContext
|
|
37
38
|
from morango.sync.context import LocalSessionContext
|
|
38
39
|
from morango.sync.context import NetworkSessionContext
|
|
40
|
+
from morango.sync.controller import SessionController
|
|
39
41
|
from morango.sync.utils import SyncSignal
|
|
40
42
|
from morango.sync.utils import SyncSignalGroup
|
|
41
43
|
from morango.utils import CAPABILITIES
|
|
42
44
|
from morango.utils import pid_exists
|
|
45
|
+
from morango.sync.utils import lock_partitions
|
|
43
46
|
|
|
44
47
|
if GZIP_BUFFER_POST in CAPABILITIES:
|
|
45
48
|
from gzip import GzipFile
|
|
@@ -47,6 +50,7 @@ if GZIP_BUFFER_POST in CAPABILITIES:
|
|
|
47
50
|
|
|
48
51
|
logger = logging.getLogger(__name__)
|
|
49
52
|
|
|
53
|
+
DBBackend = load_backend(connection)
|
|
50
54
|
|
|
51
55
|
def _join_with_logical_operator(lst, operator):
|
|
52
56
|
op = ") {operator} (".format(operator=operator)
|
|
@@ -224,6 +228,14 @@ class NetworkSyncConnection(Connection):
|
|
|
224
228
|
if not server_cert.verify(message, session_resp.json().get("signature")):
|
|
225
229
|
raise CertificateSignatureInvalid()
|
|
226
230
|
|
|
231
|
+
client_instance = InstanceIDModel.get_or_create_current_instance()[0]
|
|
232
|
+
|
|
233
|
+
server_instance_json = session_resp.json().get("server_instance") or "{}"
|
|
234
|
+
server_instance_id = None
|
|
235
|
+
if server_instance_json:
|
|
236
|
+
server_instance = json.loads(server_instance_json)
|
|
237
|
+
server_instance_id = server_instance.get("id")
|
|
238
|
+
|
|
227
239
|
# build the data to be used for creating our own syncsession
|
|
228
240
|
data = {
|
|
229
241
|
"id": data["id"],
|
|
@@ -238,23 +250,29 @@ class NetworkSyncConnection(Connection):
|
|
|
238
250
|
"connection_path": self.base_url,
|
|
239
251
|
"client_ip": data["client_ip"],
|
|
240
252
|
"server_ip": data["server_ip"],
|
|
241
|
-
"
|
|
253
|
+
"client_instance_id": client_instance.id,
|
|
254
|
+
"client_instance_json": json.dumps(
|
|
242
255
|
InstanceIDSerializer(
|
|
243
|
-
|
|
256
|
+
client_instance
|
|
244
257
|
).data
|
|
245
258
|
),
|
|
246
|
-
"
|
|
259
|
+
"server_instance_id": server_instance_id,
|
|
260
|
+
"server_instance_json": session_resp.json().get("server_instance") or "{}",
|
|
247
261
|
"process_id": os.getpid(),
|
|
248
262
|
}
|
|
249
263
|
sync_session = SyncSession.objects.create(**data)
|
|
250
264
|
return SyncSessionClient(self, sync_session)
|
|
251
265
|
|
|
252
|
-
def resume_sync_session(self, sync_session_id, chunk_size=None):
|
|
266
|
+
def resume_sync_session(self, sync_session_id, chunk_size=None, ignore_existing_process=False):
|
|
253
267
|
"""
|
|
254
268
|
Resumes an existing sync session given an ID
|
|
255
269
|
|
|
256
270
|
:param sync_session_id: The UUID of the `SyncSession` to resume
|
|
257
271
|
:param chunk_size: An optional parameter specifying the size for each transferred chunk
|
|
272
|
+
:type chunk_size: int
|
|
273
|
+
:param ignore_existing_process:An optional parameter specifying whether to ignore an
|
|
274
|
+
existing active process ID
|
|
275
|
+
:type ignore_existing_process: bool
|
|
258
276
|
:return: A SyncSessionClient instance
|
|
259
277
|
:rtype: SyncSessionClient
|
|
260
278
|
"""
|
|
@@ -270,7 +288,8 @@ class NetworkSyncConnection(Connection):
|
|
|
270
288
|
|
|
271
289
|
# check that process of existing session isn't still running
|
|
272
290
|
if (
|
|
273
|
-
|
|
291
|
+
not ignore_existing_process
|
|
292
|
+
and sync_session.process_id
|
|
274
293
|
and sync_session.process_id != os.getpid()
|
|
275
294
|
and pid_exists(sync_session.process_id)
|
|
276
295
|
):
|
|
@@ -283,7 +302,7 @@ class NetworkSyncConnection(Connection):
|
|
|
283
302
|
try:
|
|
284
303
|
self._get_sync_session(sync_session)
|
|
285
304
|
except HTTPError as e:
|
|
286
|
-
|
|
305
|
+
raise MorangoResumeSyncError("Failure resuming sync session") from e
|
|
287
306
|
|
|
288
307
|
# update process id
|
|
289
308
|
sync_session.process_id = os.getpid()
|
|
@@ -337,11 +356,15 @@ class NetworkSyncConnection(Connection):
|
|
|
337
356
|
cert_chain_response = self._get_certificate_chain(
|
|
338
357
|
params={"ancestors_of": parent_cert.id}
|
|
339
358
|
)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
359
|
+
cert_chain = cert_chain_response.json()
|
|
360
|
+
with transaction.atomic():
|
|
361
|
+
lock_partitions(DBBackend, sync_filter=Filter(cert_chain[0]["id"]))
|
|
362
|
+
# check again, now that we have a lock
|
|
363
|
+
if not Certificate.objects.filter(id=parent_cert.id).exists():
|
|
364
|
+
# upon receiving cert chain from server, we attempt to save the chain into our records
|
|
365
|
+
Certificate.save_certificate_chain(
|
|
366
|
+
cert_chain, expected_last_id=parent_cert.id
|
|
367
|
+
)
|
|
345
368
|
|
|
346
369
|
csr_key = Key()
|
|
347
370
|
# build up data for csr
|
|
@@ -424,7 +447,7 @@ class NetworkSyncConnection(Connection):
|
|
|
424
447
|
# convert user arguments into query str for passing to auth layer
|
|
425
448
|
if isinstance(userargs, dict):
|
|
426
449
|
userargs = "&".join(
|
|
427
|
-
["{}={}".format(key, val) for (key, val) in
|
|
450
|
+
["{}={}".format(key, val) for (key, val) in userargs.items()]
|
|
428
451
|
)
|
|
429
452
|
return self.session.post(
|
|
430
453
|
self.urlresolve(api_urls.CERTIFICATE), json=data, auth=(userargs, password)
|
|
@@ -575,10 +598,8 @@ class TransferClient(object):
|
|
|
575
598
|
"sync_connection",
|
|
576
599
|
"sync_session",
|
|
577
600
|
"controller",
|
|
578
|
-
"current_transfer_session",
|
|
579
601
|
"signals",
|
|
580
|
-
"
|
|
581
|
-
"remote_context",
|
|
602
|
+
"context",
|
|
582
603
|
)
|
|
583
604
|
|
|
584
605
|
def __init__(self, sync_connection, sync_session, controller):
|
|
@@ -590,51 +611,45 @@ class TransferClient(object):
|
|
|
590
611
|
self.sync_connection = sync_connection
|
|
591
612
|
self.sync_session = sync_session
|
|
592
613
|
self.controller = controller
|
|
593
|
-
self.current_transfer_session = None
|
|
594
614
|
self.signals = SyncClientSignals()
|
|
595
615
|
|
|
596
|
-
|
|
597
|
-
self.
|
|
598
|
-
sync_connection,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
sync_session=sync_session, capabilities=self.remote_context.capabilities
|
|
616
|
+
capabilities = sync_connection.server_info.get("capabilities", [])
|
|
617
|
+
self.context = CompositeSessionContext(
|
|
618
|
+
[LocalSessionContext(), NetworkSessionContext(sync_connection)],
|
|
619
|
+
sync_session=sync_session,
|
|
620
|
+
capabilities=capabilities,
|
|
602
621
|
)
|
|
622
|
+
self.controller.context = self.context
|
|
603
623
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
624
|
+
@property
|
|
625
|
+
def current_transfer_session(self):
|
|
626
|
+
return self.context.transfer_session
|
|
627
|
+
|
|
628
|
+
def proceed_to_and_wait_for(self, stage, error_msg=None, callback=None):
|
|
629
|
+
"""
|
|
630
|
+
Raises an exception if an ERROR result is received from calling `proceed_to_and_wait_for`
|
|
631
|
+
:param stage: The stage to proceed to
|
|
632
|
+
:param error_msg: An error message str to use as the exception message if it errors
|
|
633
|
+
:param callback: A callback to pass along to the controller
|
|
634
|
+
"""
|
|
635
|
+
result = self.controller.proceed_to_and_wait_for(stage, callback=callback)
|
|
636
|
+
if result == transfer_statuses.ERRORED:
|
|
637
|
+
raise MorangoError(
|
|
638
|
+
error_msg or "Stage `{}` failed".format(self.context.stage)
|
|
639
|
+
) from self.context.error
|
|
616
640
|
|
|
617
641
|
def initialize(self, sync_filter):
|
|
618
642
|
"""
|
|
619
643
|
:param sync_filter: Filter
|
|
620
644
|
"""
|
|
621
645
|
# set filter on controller
|
|
622
|
-
self.
|
|
623
|
-
self.remote_context.update(sync_filter=sync_filter)
|
|
646
|
+
self.context.update(sync_filter=sync_filter)
|
|
624
647
|
|
|
625
|
-
# initialize the transfer session
|
|
626
|
-
|
|
627
|
-
transfer_stages.INITIALIZING,
|
|
648
|
+
# initialize the transfer session
|
|
649
|
+
self.proceed_to_and_wait_for(
|
|
650
|
+
transfer_stages.INITIALIZING,
|
|
651
|
+
error_msg="Failed to initialize transfer session",
|
|
628
652
|
)
|
|
629
|
-
if status == transfer_statuses.ERRORED:
|
|
630
|
-
raise_from(
|
|
631
|
-
MorangoError("Failed to initialize transfer session"),
|
|
632
|
-
self.local_context.error,
|
|
633
|
-
)
|
|
634
|
-
|
|
635
|
-
# copy the transfer session to local state and update remote controller context
|
|
636
|
-
self.current_transfer_session = self.local_context.transfer_session
|
|
637
|
-
self.remote_context.update(transfer_session=self.current_transfer_session)
|
|
638
653
|
|
|
639
654
|
self.signals.session.started.fire(
|
|
640
655
|
transfer_session=self.current_transfer_session
|
|
@@ -643,21 +658,21 @@ class TransferClient(object):
|
|
|
643
658
|
# backwards compatibility for the queuing signal as it included both serialization
|
|
644
659
|
# and queuing originally
|
|
645
660
|
with self.signals.queuing.send(transfer_session=self.current_transfer_session):
|
|
646
|
-
# proceeding to
|
|
647
|
-
self.proceed_to_and_wait_for(transfer_stages.SERIALIZING)
|
|
661
|
+
# proceeding to queuing on remote will trigger initialization and serialization as well
|
|
648
662
|
self.proceed_to_and_wait_for(transfer_stages.QUEUING)
|
|
649
663
|
|
|
650
664
|
def run(self):
|
|
665
|
+
"""
|
|
666
|
+
Execute the transferring portion of the sync
|
|
667
|
+
"""
|
|
651
668
|
with self.signals.transferring.send(
|
|
652
669
|
transfer_session=self.current_transfer_session
|
|
653
670
|
) as status:
|
|
654
|
-
self.
|
|
671
|
+
self.proceed_to_and_wait_for(
|
|
672
|
+
transfer_stages.TRANSFERRING, callback=status.in_progress.fire
|
|
673
|
+
)
|
|
655
674
|
|
|
656
675
|
def finalize(self):
|
|
657
|
-
# if not initialized, we don't need to finalize
|
|
658
|
-
if not self.current_transfer_session:
|
|
659
|
-
return
|
|
660
|
-
|
|
661
676
|
with self.signals.dequeuing.send(
|
|
662
677
|
transfer_session=self.current_transfer_session
|
|
663
678
|
):
|
|
@@ -667,26 +682,6 @@ class TransferClient(object):
|
|
|
667
682
|
self.signals.session.completed.fire(
|
|
668
683
|
transfer_session=self.current_transfer_session
|
|
669
684
|
)
|
|
670
|
-
self.current_transfer_session = None
|
|
671
|
-
|
|
672
|
-
def _transfer(self, callback=None):
|
|
673
|
-
result = transfer_statuses.PENDING
|
|
674
|
-
|
|
675
|
-
while result not in transfer_statuses.FINISHED_STATES:
|
|
676
|
-
result = self.controller.proceed_to(
|
|
677
|
-
transfer_stages.TRANSFERRING, context=self.remote_context
|
|
678
|
-
)
|
|
679
|
-
self.local_context.update(
|
|
680
|
-
stage=transfer_stages.TRANSFERRING, stage_status=result
|
|
681
|
-
)
|
|
682
|
-
if callback is not None:
|
|
683
|
-
callback()
|
|
684
|
-
|
|
685
|
-
if result == transfer_statuses.ERRORED:
|
|
686
|
-
raise_from(
|
|
687
|
-
MorangoError("Failure occurred during transfer"),
|
|
688
|
-
self.remote_context.error,
|
|
689
|
-
)
|
|
690
685
|
|
|
691
686
|
|
|
692
687
|
class PushClient(TransferClient):
|
|
@@ -696,8 +691,7 @@ class PushClient(TransferClient):
|
|
|
696
691
|
|
|
697
692
|
def __init__(self, *args, **kwargs):
|
|
698
693
|
super(PushClient, self).__init__(*args, **kwargs)
|
|
699
|
-
self.
|
|
700
|
-
self.remote_context.update(is_push=True)
|
|
694
|
+
self.context.update(is_push=True)
|
|
701
695
|
|
|
702
696
|
|
|
703
697
|
class PullClient(TransferClient):
|
|
@@ -707,5 +701,4 @@ class PullClient(TransferClient):
|
|
|
707
701
|
|
|
708
702
|
def __init__(self, *args, **kwargs):
|
|
709
703
|
super(PullClient, self).__init__(*args, **kwargs)
|
|
710
|
-
self.
|
|
711
|
-
self.remote_context.update(is_push=False)
|
|
704
|
+
self.context.update(is_push=False)
|
morango/sync/utils.py
CHANGED
|
@@ -230,3 +230,21 @@ class SyncSignalGroup(SyncSignal):
|
|
|
230
230
|
Fires the `completed` signal.
|
|
231
231
|
"""
|
|
232
232
|
self.completed.fire()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def lock_partitions(backend, sync_filter=None, shared=False):
|
|
236
|
+
"""
|
|
237
|
+
Lock all partitions for a filter, and really lock everything if the filter is null
|
|
238
|
+
|
|
239
|
+
:type backend: morango.sync.backends.base.BaseSQLWrapper
|
|
240
|
+
:type sync_filter: morango.models.certificates.Filter|None
|
|
241
|
+
:type shared: bool
|
|
242
|
+
"""
|
|
243
|
+
if sync_filter is None:
|
|
244
|
+
backend._lock_all_partitions(shared=shared)
|
|
245
|
+
else:
|
|
246
|
+
# parse the first 32 chars to obtain the primary partitions in the filter
|
|
247
|
+
partitions = set(f[:32] for f in sync_filter)
|
|
248
|
+
# for every unique partition, acquire a lock
|
|
249
|
+
for partition in partitions:
|
|
250
|
+
backend._lock_partition(partition, shared=shared)
|
morango/urls.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from django.
|
|
2
|
-
from django.
|
|
1
|
+
from django.urls import include
|
|
2
|
+
from django.urls import path
|
|
3
3
|
|
|
4
|
-
urlpatterns = [
|
|
4
|
+
urlpatterns = [path("api/morango/v1/", include("morango.api.urls"))]
|
morango/utils.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
|
-
import six
|
|
3
2
|
from importlib import import_module
|
|
4
3
|
|
|
5
4
|
from django.conf import settings
|
|
6
5
|
|
|
7
6
|
from morango.constants import settings as default_settings
|
|
8
7
|
from morango.constants.capabilities import ALLOW_CERTIFICATE_PUSHING
|
|
9
|
-
from morango.constants.capabilities import GZIP_BUFFER_POST
|
|
10
8
|
from morango.constants.capabilities import ASYNC_OPERATIONS
|
|
11
9
|
from morango.constants.capabilities import FSIC_V2_FORMAT
|
|
10
|
+
from morango.constants.capabilities import GZIP_BUFFER_POST
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
def do_import(import_string):
|
|
@@ -32,7 +31,7 @@ class Settings(object):
|
|
|
32
31
|
def __getattribute__(self, key):
|
|
33
32
|
"""Coalesces settings with the defaults"""
|
|
34
33
|
value = getattr(settings, key, getattr(default_settings, key, None))
|
|
35
|
-
if key == "MORANGO_INSTANCE_INFO" and isinstance(value,
|
|
34
|
+
if key == "MORANGO_INSTANCE_INFO" and isinstance(value, str):
|
|
36
35
|
value = dict(do_import(value))
|
|
37
36
|
return value
|
|
38
37
|
|
|
@@ -1,30 +1,47 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: morango
|
|
3
|
-
Version: 0.6
|
|
3
|
+
Version: 0.8.6
|
|
4
4
|
Summary: Pure Python sqlite-based Django DB replication engine.
|
|
5
5
|
Home-page: https://github.com/learningequality/morango
|
|
6
6
|
Author: Learning Equality
|
|
7
7
|
Author-email: dev@learningequality.org
|
|
8
8
|
License: MIT
|
|
9
9
|
Keywords: database,syncing,morango
|
|
10
|
-
Platform: UNKNOWN
|
|
11
10
|
Classifier: Intended Audience :: Developers
|
|
12
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
12
|
Classifier: Natural Language :: English
|
|
14
13
|
Classifier: Development Status :: 4 - Beta
|
|
15
|
-
Classifier: Programming Language :: Python :: 2.7
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.4
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.5
|
|
18
14
|
Classifier: Programming Language :: Python :: 3.6
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Requires-Python: >=3.6, <3.14
|
|
19
23
|
Description-Content-Type: text/markdown
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
License-File: AUTHORS.md
|
|
26
|
+
Requires-Dist: django<4,>=3
|
|
27
|
+
Requires-Dist: django-mptt>0.10.0
|
|
28
|
+
Requires-Dist: rsa<4.10
|
|
29
|
+
Requires-Dist: djangorestframework>3.10
|
|
30
|
+
Requires-Dist: django-ipware==4.0.2
|
|
26
31
|
Requires-Dist: requests
|
|
27
32
|
Requires-Dist: ifcfg
|
|
33
|
+
Dynamic: author
|
|
34
|
+
Dynamic: author-email
|
|
35
|
+
Dynamic: classifier
|
|
36
|
+
Dynamic: description
|
|
37
|
+
Dynamic: description-content-type
|
|
38
|
+
Dynamic: home-page
|
|
39
|
+
Dynamic: keywords
|
|
40
|
+
Dynamic: license
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
Dynamic: requires-dist
|
|
43
|
+
Dynamic: requires-python
|
|
44
|
+
Dynamic: summary
|
|
28
45
|
|
|
29
46
|
# Morango
|
|
30
47
|
|
|
@@ -57,5 +74,3 @@ make docs
|
|
|
57
74
|
# auto-build and refresh docs on edit
|
|
58
75
|
make docs-autobuild
|
|
59
76
|
```
|
|
60
|
-
|
|
61
|
-
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
morango/__init__.py,sha256=VpASnrti7EGWxUfSWGgERUfe7NLJltfVXYosOzHbpPg,22
|
|
2
|
+
morango/apps.py,sha256=bs_LaTt1038Oh0OOWqaZKpF8rL9cg8rqW4hftIILPw8,559
|
|
3
|
+
morango/errors.py,sha256=okjt7FqSHIf3x9SmnmoW6aqNJ_g7Ye3Fx7tuQSNUYEI,1223
|
|
4
|
+
morango/proquint.py,sha256=YRjgceaYD_wDE8R-21AK5m6MSzbarIBP_kDxJ_gHsbQ,3679
|
|
5
|
+
morango/registry.py,sha256=LcHyrNqcqSBAEHJDMS0pinKKzkxRTZjxEyAeZhtrKgo,9480
|
|
6
|
+
morango/urls.py,sha256=pIjnq01BUNxnC0BucRXMr_0H366U5l88k65r67JtHK8,131
|
|
7
|
+
morango/utils.py,sha256=psppzkIjlrmWI2bq2a1wGW0f_0VhdszYleLbm4C4aAs,3652
|
|
8
|
+
morango/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
morango/api/fields.py,sha256=LjEuUrF86JABN45HSXHpUoWHHBDeb73jqD_uS6QY0EA,273
|
|
10
|
+
morango/api/parsers.py,sha256=PxNHz94V3VrSCEVYxuTRO2h8mTg9H7WSy891BxUvsac,557
|
|
11
|
+
morango/api/permissions.py,sha256=B3biuWZfm1VXbIO5pKR2jYBNQBfgY2oKfX3dargtb6A,3550
|
|
12
|
+
morango/api/serializers.py,sha256=GtG_6wcJst68o3NHuIi2q_pX9cOIIIrqmHDZRgR-BdE,5444
|
|
13
|
+
morango/api/urls.py,sha256=D3VFRf4wm1u-Pswu6ZGqJoG-UHQFTI7GGXcqNyz0t5Y,1037
|
|
14
|
+
morango/api/viewsets.py,sha256=qxc4RhQrnJKVAq6F1X9Yo1tv3eGmvF0QJ8-gBrR9lOA,20859
|
|
15
|
+
morango/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
morango/constants/api_urls.py,sha256=mmGJyyVtrMxjIMAF2iBHbjGVndsvW_z1IQH-GhW3L-g,453
|
|
17
|
+
morango/constants/capabilities.py,sha256=KaTQpfpg6l4G0aGXJnOKi7ibbshdg-1fA9aLl-Qc_Oc,166
|
|
18
|
+
morango/constants/settings.py,sha256=dygRwRZhYkTdRhEltc5QqdoQBLkuZ-f9fdFMVrCaDpQ,2009
|
|
19
|
+
morango/constants/transfer_stages.py,sha256=mp-1V22ba0gKdylJgnUJ8Jh-Qs9-3-LsY_Q_cxRUgmI,1378
|
|
20
|
+
morango/constants/transfer_statuses.py,sha256=BU3_dx7RxXpWF3pDjC9JsDe2xzKSGiqZFVBbdIY7_aQ,509
|
|
21
|
+
morango/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
morango/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
morango/management/commands/cleanupsyncs.py,sha256=Huaz8etLNbmW8X-Vy4x1QY801JYln0iyaeUTJF2L3kE,4833
|
|
24
|
+
morango/migrations/0001_initial.py,sha256=ZFEKusKCIUsgUuHpF-eWqEqu7FrhO2ikkfu5KfYfXx0,11892
|
|
25
|
+
morango/migrations/0001_squashed_0024_auto_20240129_1757.py,sha256=K-71iKz9cKpb-zrxuoDJVWafZM3U_hM6EpX6aNzsFjM,21770
|
|
26
|
+
morango/migrations/0002_auto_20170511_0400.py,sha256=9EQcTl2xHd-rwx_NWCLzo8GU3u7MFA3WOryyKYEbHUw,2263
|
|
27
|
+
morango/migrations/0002_store_idx_morango_deserialize.py,sha256=AbfcuoovuViGRugR5lH86E3JgfgBa_4koTp5vMiUAh8,568
|
|
28
|
+
morango/migrations/0003_auto_20170519_0543.py,sha256=5YfHW6IcT57tgaXW6pnWHmL6kfgauTW2nao9zQr3nP8,641
|
|
29
|
+
morango/migrations/0004_auto_20170520_2112.py,sha256=T7rGjSPXUGnanDr5SJKj5SHZEQYSIaND60vRaK3DObw,517
|
|
30
|
+
morango/migrations/0005_auto_20170629_2139.py,sha256=PL1I3wybrDgDxY8hSmZmUfpxOulr6O2ywui7ue_dvKw,654
|
|
31
|
+
morango/migrations/0006_instanceidmodel_system_id.py,sha256=L4Y5VN1-IBnBPs-lyQrJronf5lrXjvwGFSTKuug-jjo,435
|
|
32
|
+
morango/migrations/0007_auto_20171018_1615.py,sha256=otylWm8eBH8W_fJx-W5C6e8mfhqPdrb-6SZh0GCpFZo,8149
|
|
33
|
+
morango/migrations/0008_auto_20171114_2217.py,sha256=x_2rKsqfH-yRHmuV3qnBgPRBBe0C2Z08aryWcBZU_jw,2085
|
|
34
|
+
morango/migrations/0009_auto_20171205_0252.py,sha256=XxkPK4AsERDw6ZBojS0M1ElMTuhVjxRWDSMDMnvPbDM,359
|
|
35
|
+
morango/migrations/0010_auto_20171206_1615.py,sha256=2Svr2sMBO31fOzm26HdgAL-YmHGOzgWnDMEuoaDWrOU,800
|
|
36
|
+
morango/migrations/0011_sharedkey.py,sha256=fZkD4-2d628Cg0-ekvQnI_CAIVCuei4R2zre9_i87ms,926
|
|
37
|
+
morango/migrations/0012_auto_20180927_1658.py,sha256=29ibsHWltRyUk3lXR3ZLzmeCY2SRWpW7KXU8zmTzIfk,946
|
|
38
|
+
morango/migrations/0013_auto_20190627_1513.py,sha256=eG6pdMh_xqz9nfn9z0F987rR12i6JswSFFKCjsHwsCQ,464
|
|
39
|
+
morango/migrations/0014_syncsession_extra_fields.py,sha256=11qg91F0ZCaxh3AS17xEUH5xlxBDyWSi3RRgbE593xE,440
|
|
40
|
+
morango/migrations/0015_auto_20200508_2104.py,sha256=nD3FkddbgWYDoIau79X5-gq_xMjlJV_EQF14MYff-Fc,667
|
|
41
|
+
morango/migrations/0016_store_deserialization_error.py,sha256=yGenUzUrcIzWDR2_7h0tU_8M-2tBC2-ldjcPrV6510M,441
|
|
42
|
+
morango/migrations/0017_store_last_transfer_session_id.py,sha256=oVQUlvo247JgecQ2NY-Xb5kyoK4rRhb09YbrRj1enmo,521
|
|
43
|
+
morango/migrations/0018_auto_20210714_2216.py,sha256=ghAZmcFXqlWPuSiFt-ikwfZJg-3IKOXiC_sa6nbzme0,1599
|
|
44
|
+
morango/migrations/0019_auto_20220113_1807.py,sha256=jRbNz8OQScW4KN-JsY_KZb7yy5kSO30iWhSAhCHC6w4,1521
|
|
45
|
+
morango/migrations/0020_postgres_fix_nullable.py,sha256=QHgSdEzmIzpLn0HXDWRepmBhvc7S-1emErNQVyrYAfA,1206
|
|
46
|
+
morango/migrations/0021_store_partition_index_create.py,sha256=B9p20AQA0BVqlfmaPYd4RAghYtJKvrdzafWIUAbV7w0,2209
|
|
47
|
+
morango/migrations/0022_rename_instance_fields.py,sha256=xn2PGlgkHOPRYBHrbGdh6XGGHl_X5HPFEIgmGIG0Ycw,592
|
|
48
|
+
morango/migrations/0023_add_instance_id_fields.py,sha256=ovO_AVGLPlLpEc72UhrJ0TpFYCci_CzDcEdmnNJzLew,635
|
|
49
|
+
morango/migrations/0024_auto_20240129_1757.py,sha256=Buy1T4PiTFo8zP7p2HY-3gmiJurQsSnu7CBgTSvOxXg,762
|
|
50
|
+
morango/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
+
morango/models/__init__.py,sha256=ws6VwRPs7iKLRfFSuRpUESTZlGEmSk0Nv61chh-Gn2Q,1722
|
|
52
|
+
morango/models/certificates.py,sha256=4s7YTNhmTXLyPuhpZZay-Dei8pdq2WqlOshNwGxzlOE,19380
|
|
53
|
+
morango/models/core.py,sha256=-yZ01bS0nXNV0BL_wTHEc4x-7Hh_69hn3MyUunGK2SU,39080
|
|
54
|
+
morango/models/fsic_utils.py,sha256=B9RM-uPZFvEXWbsPeeit5qA21gKGpA0NkKCLFIdNI_0,7130
|
|
55
|
+
morango/models/manager.py,sha256=fzzX7k8qV5W5dMBb0ASOBNRJRpvQZbEG1hgyuMtzt4g,163
|
|
56
|
+
morango/models/query.py,sha256=eHcf9PYYEDShN6lRng7Ow9RJwNFPdKI1hDbVUXeyl9U,869
|
|
57
|
+
morango/models/signals.py,sha256=R17Fqddbg2AgAVnH4oZPrDQMWi94R2XsLmnUbrgKS2I,478
|
|
58
|
+
morango/models/utils.py,sha256=8J138TR3FkslovfUWkLLEpODieFtmHKne7Lm1I8iPXk,6588
|
|
59
|
+
morango/models/fields/__init__.py,sha256=dzxg8-_v3irXkPnyCsiVDur0gG0RJfKzFcshjL6Y-9s,225
|
|
60
|
+
morango/models/fields/crypto.py,sha256=Q_y7s-UpFEz9zXFaAkEDOmqwx4b19F7wbTafpd_XPp4,14509
|
|
61
|
+
morango/models/fields/uuids.py,sha256=w7M8KlA8oLPascq14tcor89V92IGxx2XtdweS-0lF6A,4275
|
|
62
|
+
morango/sync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
|
+
morango/sync/context.py,sha256=r3sHkmb7OYCqLm3C0ADv2qvdXeZHTBwLGe6wkMdOMGc,18163
|
|
64
|
+
morango/sync/controller.py,sha256=ZfvXFNptduxhtF72E07o9Dh3Fcnk7xSyjvMTZ---oZw,11666
|
|
65
|
+
morango/sync/operations.py,sha256=ErZ-W4MiCUVl_vn0pFi8DWGgT1C9wOAH4AagCMdLBOs,69502
|
|
66
|
+
morango/sync/session.py,sha256=9CTLhpEPM8q1M_b2HHxouZbzm7vBJoImoqPWqdodgII,3853
|
|
67
|
+
morango/sync/syncsession.py,sha256=fhm7r3DHeBXDSWG0k7I2slhgxe3ebtmXouZHTzUQ9cs,25846
|
|
68
|
+
morango/sync/utils.py,sha256=BrG8CYVmaAhRNa_HYuPCYSnncskSsW_ig0Cf7xsY2mo,8792
|
|
69
|
+
morango/sync/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
70
|
+
morango/sync/backends/base.py,sha256=zQP5hOB2Qw0qv0G00LXFHl2GQ3b0bheyujdNVl5rT-8,10867
|
|
71
|
+
morango/sync/backends/postgres.py,sha256=N0IeXN67AkdQKlD20b8LQZ1ocdarapL8pqu8FQAc6lM,19850
|
|
72
|
+
morango/sync/backends/sqlite.py,sha256=vg7kWFIF346-NjVwexYfjYbrrOyzzvcCmk5uUkqhabw,12850
|
|
73
|
+
morango/sync/backends/utils.py,sha256=UvyYByzRenOwtDvLPO_1wIOE8Yr88FnuAM2c4VSk7uQ,5145
|
|
74
|
+
morango-0.8.6.dist-info/licenses/AUTHORS.md,sha256=9Ussd3Fq3RPjHyQT_3AyGss5jiVbn858WQuIBOi1ajI,276
|
|
75
|
+
morango-0.8.6.dist-info/licenses/LICENSE,sha256=RkI6MjmrrTKa3aoPAVyk9QdeLZclcrKds6FH3phRSRU,1074
|
|
76
|
+
morango-0.8.6.dist-info/METADATA,sha256=Qy4IvvYvDLsslhq2TzulHvZDzqEBvPBhMjFCwpBi0IQ,2819
|
|
77
|
+
morango-0.8.6.dist-info/WHEEL,sha256=AeO2BvogYWm3eGaHCvhzmUYt8ia7KfURiHzO_1atlys,109
|
|
78
|
+
morango-0.8.6.dist-info/top_level.txt,sha256=pzREWN0EeEq3yHDRast9XI081Ow_rtcm7WV0ZQTlIPY,8
|
|
79
|
+
morango-0.8.6.dist-info/RECORD,,
|
morango/models/morango_mptt.py
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from mptt import managers
|
|
2
|
-
from mptt import models
|
|
3
|
-
from mptt import querysets
|
|
4
|
-
|
|
5
|
-
from .manager import SyncableModelManager
|
|
6
|
-
from .query import SyncableModelQuerySet
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class MorangoTreeQuerySet(querysets.TreeQuerySet, SyncableModelQuerySet):
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class MorangoMPTTTreeManager(managers.TreeManager, SyncableModelManager):
|
|
14
|
-
def get_queryset(self):
|
|
15
|
-
return MorangoTreeQuerySet(self.model, using=self._db)
|
|
16
|
-
|
|
17
|
-
def _mptt_update(self, qs=None, **items):
|
|
18
|
-
items["update_dirty_bit_to"] = None
|
|
19
|
-
return super(MorangoMPTTTreeManager, self)._mptt_update(qs, **items)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class MorangoMPTTModel(models.MPTTModel):
|
|
23
|
-
"""
|
|
24
|
-
Any model that inherits from ``SyncableModel`` that also wants to inherit from ``MPTTModel`` should instead inherit
|
|
25
|
-
from ``MorangoMPTTModel``, which modifies some behavior to make it safe for the syncing system.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
_internal_mptt_fields_not_to_serialize = ("lft", "rght", "tree_id", "level")
|
|
29
|
-
|
|
30
|
-
objects = MorangoMPTTTreeManager()
|
|
31
|
-
|
|
32
|
-
class Meta:
|
|
33
|
-
abstract = True
|