diffsync 1.9.0__tar.gz → 2.0.0__tar.gz

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.
@@ -1,6 +1,27 @@
1
1
  # Changelog
2
2
 
3
- ## v1.9.0 - 2023-10-16
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+
7
+ ## [2.0.0]
8
+
9
+ ### Changed
10
+
11
+ - **BREAKING CHANGE** #236/240 - Upgrade to Pydantic v2.
12
+
13
+ ## [1.10.0] - 2023-11-16
14
+
15
+ ### Fixed
16
+
17
+ - #249 - Fixes natural deletion order flag
18
+ - #247 - Fixes underspecified typing_extensions dependency
19
+
20
+ ### Changed
21
+
22
+ - #247 - Deprecates Python 3.7
23
+
24
+ ## [1.9.0] - 2023-10-16
4
25
 
5
26
  ### Added
6
27
 
@@ -10,7 +31,7 @@
10
31
 
11
32
  - #219 - Type hinting overhaul
12
33
 
13
- ## v1.8.0 - 2023-04-18
34
+ ## [1.8.0] - 2023-04-18
14
35
 
15
36
  ### Added
16
37
 
@@ -23,7 +44,7 @@
23
44
  - #77/#188 - `sync_from()` and `sync_to()` now return the `Diff` that was applied.
24
45
  - #211 - Loosened `packaging` and `structlog` library dependency constraints for broader compatibility.
25
46
 
26
- ## v1.7.0 - 2022-11-03
47
+ ## [1.7.0] - 2022-11-03
27
48
 
28
49
  ### Changed
29
50
 
@@ -41,15 +62,15 @@
41
62
 
42
63
  ### Fixed
43
64
 
44
- - #149 Limit redundant CI concurrency
65
+ - #149 - Limit redundant CI concurrency
45
66
 
46
- ## v1.6.0 - 2022-07-09
67
+ ## [1.6.0] - 2022-07-09
47
68
 
48
69
  ### Changed
49
70
 
50
71
  - #120 - Dropped support for Python 3.6, new minimum is Python 3.7
51
72
 
52
- ## v1.5.1 - 2022-06-30
73
+ ## [1.5.1] - 2022-06-30
53
74
 
54
75
  ### Added
55
76
 
@@ -64,13 +85,13 @@
64
85
  - #115 - Fixed ReadTheDocs rendering pipeline
65
86
  - #118 - Fixed a regression in `DiffSync.get(modelname, identifiers)` introduced in 1.5.0
66
87
 
67
- ## v1.5.0 - 2022-06-07
88
+ ## [1.5.0] - 2022-06-07
68
89
 
69
90
  ### Added
70
91
 
71
92
  - #106 - Add a new, optional, backend store based in Redis
72
93
 
73
- ## v1.4.3 - 2022-03-03
94
+ ## [1.4.3] - 2022-03-03
74
95
 
75
96
  ### Fixed
76
97
 
@@ -80,9 +101,9 @@
80
101
 
81
102
  ### Changed
82
103
 
83
- - #103 Update development dependencies
104
+ - #103 - Update development dependencies
84
105
 
85
- ## v1.4.2 - 2022-02-28
106
+ ## [1.4.2] - 2022-02-28
86
107
 
87
108
  **WARNING** - #90 inadvertently introduced a breaking API change in DiffSync 1.4.0 through 1.4.2 (#101); this change was reverted in #102 for DiffSync 1.4.3 and later. We recommend not using this release, and moving to 1.4.3 instead.
88
109
 
@@ -90,7 +111,7 @@
90
111
 
91
112
  - #100 - Added explicit dependency on `packaging`.
92
113
 
93
- ## v1.4.1 - 2022-01-26
114
+ ## [1.4.1] - 2022-01-26
94
115
 
95
116
  **WARNING** - #90 inadvertently introduced a breaking API change in DiffSync 1.4.0 through 1.4.2 (#101); this change was reverted in #102 for DiffSync 1.4.3 and later. We recommend not using this release, and moving to 1.4.3 instead.
96
117
 
@@ -98,7 +119,7 @@
98
119
 
99
120
  - #95 - Removed optional dependencies on `sphinx`, `m2r2`, `sphinx-rtd-theme`, `toml`.
100
121
 
101
- ## v1.4.0 - 2022-01-24
122
+ ## [1.4.0] - 2022-01-24
102
123
 
103
124
  **WARNING** - #90 inadvertently introduced a breaking API change in DiffSync 1.4.0 through 1.4.2 (#101); this change was reverted in #102 for DiffSync 1.4.3 and later. We recommend not using this release, and moving to 1.4.3 instead.
104
125
 
@@ -127,19 +148,19 @@
127
148
  - #51 - Update minimum Pydantic version due to security advisory GHSA-5jqp-qgf6-3pvh
128
149
  - #63 - Fix type in Readme
129
150
 
130
- ## v1.3.0 - 2021-04-07
151
+ ## [1.3.0] - 2021-04-07
131
152
 
132
153
  ### Added
133
154
 
134
155
  - #48 - added optional `callback` argument to `diff_from`/`diff_to`/`sync_from`/`sync_to` for use with progress reporting.
135
156
 
136
- ## v1.2.0 - 2020-12-08
157
+ ## [1.2.0] - 2020-12-08
137
158
 
138
159
  ### Added
139
160
 
140
161
  - #45 - minimum Python version lowered from 3.7 to 3.6, also now tested against Python 3.9.
141
162
 
142
- ## v1.1.0 - 2020-12-01
163
+ ## [1.1.0] - 2020-12-01
143
164
 
144
165
  ### Added
145
166
 
@@ -157,6 +178,6 @@
157
178
 
158
179
  - #44 - On CRUD failure, do not generate an extraneous "success" log message in addition to the "failed" message
159
180
 
160
- ## v1.0.0 - 2020-10-23
181
+ ## [1.0.0] - 2020-10-23
161
182
 
162
183
  Initial release
@@ -1,26 +1,27 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: diffsync
3
- Version: 1.9.0
3
+ Version: 2.0.0
4
4
  Summary: Library to easily sync/diff/update 2 different data sources
5
5
  Home-page: https://diffsync.readthedocs.io
6
6
  License: Apache-2.0
7
7
  Keywords: source-of-truth,synchronization
8
8
  Author: Network to Code, LLC
9
9
  Author-email: info@networktocode.com
10
- Requires-Python: >=3.7,<4.0
10
+ Requires-Python: >=3.8,<4.0
11
11
  Classifier: License :: OSI Approved :: Apache Software License
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.7
14
13
  Classifier: Programming Language :: Python :: 3.8
15
14
  Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
18
  Provides-Extra: redis
19
19
  Requires-Dist: colorama (>=0.4.3,<0.5.0)
20
20
  Requires-Dist: packaging (>=21.3,<24.0)
21
- Requires-Dist: pydantic (>=1.7.4,<2.0.0,!=1.8,!=1.8.1)
21
+ Requires-Dist: pydantic (>=2.0.0,<3.0.0)
22
22
  Requires-Dist: redis (>=4.3,<5.0) ; extra == "redis"
23
23
  Requires-Dist: structlog (>=20.1.0,<23.0.0)
24
+ Requires-Dist: typing-extensions (>=4.0.1) ; python_version < "3.11"
24
25
  Project-URL: Documentation, https://diffsync.readthedocs.io
25
26
  Project-URL: Repository, https://github.com/networktocode/diffsync
26
27
  Description-Content-Type: text/markdown
@@ -42,7 +43,7 @@ DiffSync is at its most useful when you have multiple sources or sets of data to
42
43
 
43
44
  # Overview of DiffSync
44
45
 
45
- DiffSync acts as an intermediate translation layer between all of the data sets you are diffing and/or syncing. In practical terms, this means that to use DiffSync, you will define a set of data models as well as the “adapters” needed to translate between each base data source and the data model. In Python terms, the adapters will be subclasses of the `DiffSync` class, and each data model class will be a subclass of the `DiffSyncModel` class.
46
+ DiffSync acts as an intermediate translation layer between all of the data sets you are diffing and/or syncing. In practical terms, this means that to use DiffSync, you will define a set of data models as well as the “adapters” needed to translate between each base data source and the data model. In Python terms, the adapters will be subclasses of the `Adapter` class, and each data model class will be a subclass of the `DiffSyncModel` class.
46
47
 
47
48
  ![Diffsync Components](https://raw.githubusercontent.com/networktocode/diffsync/develop/docs/images/diffsync_components.png "Diffsync Components")
48
49
 
@@ -15,7 +15,7 @@ DiffSync is at its most useful when you have multiple sources or sets of data to
15
15
 
16
16
  # Overview of DiffSync
17
17
 
18
- DiffSync acts as an intermediate translation layer between all of the data sets you are diffing and/or syncing. In practical terms, this means that to use DiffSync, you will define a set of data models as well as the “adapters” needed to translate between each base data source and the data model. In Python terms, the adapters will be subclasses of the `DiffSync` class, and each data model class will be a subclass of the `DiffSyncModel` class.
18
+ DiffSync acts as an intermediate translation layer between all of the data sets you are diffing and/or syncing. In practical terms, this means that to use DiffSync, you will define a set of data models as well as the “adapters” needed to translate between each base data source and the data model. In Python terms, the adapters will be subclasses of the `Adapter` class, and each data model class will be a subclass of the `DiffSyncModel` class.
19
19
 
20
20
  ![Diffsync Components](https://raw.githubusercontent.com/networktocode/diffsync/develop/docs/images/diffsync_components.png "Diffsync Components")
21
21
 
@@ -14,21 +14,43 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
+ import sys
17
18
  from inspect import isclass
18
- from typing import Callable, ClassVar, Dict, List, Optional, Tuple, Type, Union, Any, Set
19
- from typing_extensions import Self
20
-
21
- from pydantic import BaseModel, PrivateAttr
19
+ from typing import (
20
+ Callable,
21
+ ClassVar,
22
+ Dict,
23
+ List,
24
+ Optional,
25
+ Tuple,
26
+ Type,
27
+ Union,
28
+ Any,
29
+ Set,
30
+ )
31
+ import warnings
32
+
33
+ from pydantic import ConfigDict, BaseModel, PrivateAttr
22
34
  import structlog # type: ignore
23
35
 
24
36
  from diffsync.diff import Diff
25
37
  from diffsync.enum import DiffSyncModelFlags, DiffSyncFlags, DiffSyncStatus
26
- from diffsync.exceptions import DiffClassMismatch, ObjectAlreadyExists, ObjectStoreWrongType, ObjectNotFound
38
+ from diffsync.exceptions import (
39
+ DiffClassMismatch,
40
+ ObjectAlreadyExists,
41
+ ObjectStoreWrongType,
42
+ ObjectNotFound,
43
+ )
27
44
  from diffsync.helpers import DiffSyncDiffer, DiffSyncSyncer
28
45
  from diffsync.store import BaseStore
29
46
  from diffsync.store.local import LocalStore
30
47
  from diffsync.utils import get_path, set_key, tree_string
31
48
 
49
+ if sys.version_info >= (3, 11):
50
+ from typing import Self
51
+ else:
52
+ from typing_extensions import Self
53
+
32
54
  # This workaround is used because we are defining a method called `str` in our class definition, which therefore renders
33
55
  # the builtin `str` type unusable.
34
56
  StrType = str
@@ -91,8 +113,8 @@ class DiffSyncModel(BaseModel):
91
113
  Can be set as a class attribute or an instance attribute as needed.
92
114
  """
93
115
 
94
- diffsync: Optional["DiffSync"] = None
95
- """Optional: the DiffSync instance that owns this model instance."""
116
+ adapter: Optional["Adapter"] = None
117
+ """Optional: the Adapter instance that owns this model instance."""
96
118
 
97
119
  _status: DiffSyncStatus = PrivateAttr(DiffSyncStatus.SUCCESS)
98
120
  """Status of the last attempt at creating/updating/deleting this model."""
@@ -100,30 +122,27 @@ class DiffSyncModel(BaseModel):
100
122
  _status_message: str = PrivateAttr("")
101
123
  """Message, if any, associated with the create/update/delete status value."""
102
124
 
103
- class Config: # pylint: disable=too-few-public-methods
104
- """Pydantic class configuration."""
125
+ model_config = ConfigDict(arbitrary_types_allowed=True)
126
+ """Pydantic-specific configuration to allow arbitrary types on this class."""
105
127
 
106
- # Let us have a DiffSync as an instance variable even though DiffSync is not a Pydantic model itself.
107
- arbitrary_types_allowed = True
108
-
109
- def __init_subclass__(cls) -> None:
128
+ @classmethod
129
+ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
110
130
  """Validate that the various class attribute declarations correspond to actual instance fields.
111
131
 
112
132
  Called automatically on subclass declaration.
113
133
  """
114
- variables = cls.__fields__.keys()
115
134
  # Make sure that any field referenced by name actually exists on the model
116
135
  for attr in cls._identifiers:
117
- if attr not in variables and not hasattr(cls, attr):
136
+ if attr not in cls.model_fields and not hasattr(cls, attr):
118
137
  raise AttributeError(f"_identifiers {cls._identifiers} references missing or un-annotated attr {attr}")
119
138
  for attr in cls._shortname:
120
- if attr not in variables:
139
+ if attr not in cls.model_fields:
121
140
  raise AttributeError(f"_shortname {cls._shortname} references missing or un-annotated attr {attr}")
122
141
  for attr in cls._attributes:
123
- if attr not in variables:
142
+ if attr not in cls.model_fields:
124
143
  raise AttributeError(f"_attributes {cls._attributes} references missing or un-annotated attr {attr}")
125
144
  for attr in cls._children.values():
126
- if attr not in variables:
145
+ if attr not in cls.model_fields:
127
146
  raise AttributeError(f"_children {cls._children} references missing or un-annotated attr {attr}")
128
147
 
129
148
  # Any given field can only be in one of (_identifiers, _attributes, _children)
@@ -144,18 +163,18 @@ class DiffSyncModel(BaseModel):
144
163
  return self.get_unique_id()
145
164
 
146
165
  def dict(self, **kwargs: Any) -> Dict:
147
- """Convert this DiffSyncModel to a dict, excluding the diffsync field by default as it is not serializable."""
166
+ """Convert this DiffSyncModel to a dict, excluding the adapter field by default as it is not serializable."""
148
167
  if "exclude" not in kwargs:
149
- kwargs["exclude"] = {"diffsync"}
150
- return super().dict(**kwargs)
168
+ kwargs["exclude"] = {"adapter"}
169
+ return super().model_dump(**kwargs)
151
170
 
152
171
  def json(self, **kwargs: Any) -> StrType:
153
- """Convert this DiffSyncModel to a JSON string, excluding the diffsync field by default as it is not serializable."""
172
+ """Convert this DiffSyncModel to a JSON string, excluding the adapter field by default as it is not serializable."""
154
173
  if "exclude" not in kwargs:
155
- kwargs["exclude"] = {"diffsync"}
174
+ kwargs["exclude"] = {"adapter"}
156
175
  if "exclude_defaults" not in kwargs:
157
176
  kwargs["exclude_defaults"] = True
158
- return super().json(**kwargs)
177
+ return super().model_dump_json(**kwargs)
159
178
 
160
179
  def str(self, include_children: bool = True, indent: int = 0) -> StrType:
161
180
  """Build a detailed string representation of this DiffSyncModel and optionally its children."""
@@ -166,12 +185,12 @@ class DiffSyncModel(BaseModel):
166
185
  child_ids = getattr(self, fieldname)
167
186
  if not child_ids:
168
187
  output += ": []"
169
- elif not self.diffsync or not include_children:
188
+ elif not self.adapter or not include_children:
170
189
  output += f": {child_ids}"
171
190
  else:
172
191
  for child_id in child_ids:
173
192
  try:
174
- child = self.diffsync.get(modelname, child_id)
193
+ child = self.adapter.get(modelname, child_id)
175
194
  output += "\n" + child.str(include_children=include_children, indent=indent + 4)
176
195
  except ObjectNotFound:
177
196
  output += f"\n{margin} {child_id} (ERROR: details unavailable)"
@@ -183,32 +202,32 @@ class DiffSyncModel(BaseModel):
183
202
  self._status_message = message
184
203
 
185
204
  @classmethod
186
- def create_base(cls, diffsync: "DiffSync", ids: Dict, attrs: Dict) -> Optional[Self]:
205
+ def create_base(cls, adapter: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]:
187
206
  """Instantiate this class, along with any platform-specific data creation.
188
207
 
189
208
  This method is not meant to be subclassed, users should redefine create() instead.
190
209
 
191
210
  Args:
192
- diffsync: The master data store for other DiffSyncModel instances that we might need to reference
211
+ adapter: The master data store for other DiffSyncModel instances that we might need to reference
193
212
  ids: Dictionary of unique-identifiers needed to create the new object
194
213
  attrs: Dictionary of additional attributes to set on the new object
195
214
 
196
215
  Returns:
197
216
  DiffSyncModel: instance of this class.
198
217
  """
199
- model = cls(**ids, diffsync=diffsync, **attrs)
218
+ model = cls(**ids, adapter=adapter, **attrs)
200
219
  model.set_status(DiffSyncStatus.SUCCESS, "Created successfully")
201
220
  return model
202
221
 
203
222
  @classmethod
204
- def create(cls, diffsync: "DiffSync", ids: Dict, attrs: Dict) -> Optional[Self]:
223
+ def create(cls, adapter: "Adapter", ids: Dict, attrs: Dict) -> Optional[Self]:
205
224
  """Instantiate this class, along with any platform-specific data creation.
206
225
 
207
226
  Subclasses must call `super().create()` or `self.create_base()`; they may wish to then override the default status information
208
227
  by calling `set_status()` to provide more context (such as details of any interactions with underlying systems).
209
228
 
210
229
  Args:
211
- diffsync: The master data store for other DiffSyncModel instances that we might need to reference
230
+ adapter: The master data store for other DiffSyncModel instances that we might need to reference
212
231
  ids: Dictionary of unique-identifiers needed to create the new object
213
232
  attrs: Dictionary of additional attributes to set on the new object
214
233
 
@@ -219,7 +238,7 @@ class DiffSyncModel(BaseModel):
219
238
  Raises:
220
239
  ObjectNotCreated: if an error occurred.
221
240
  """
222
- return cls.create_base(diffsync=diffsync, ids=ids, attrs=attrs)
241
+ return cls.create_base(adapter=adapter, ids=ids, attrs=attrs)
223
242
 
224
243
  def update_base(self, attrs: Dict) -> Optional[Self]:
225
244
  """Base Update method to update the attributes of this instance, along with any platform-specific data updates.
@@ -375,7 +394,10 @@ class DiffSyncModel(BaseModel):
375
394
  attr_name = self._children[child_type]
376
395
  childs = getattr(self, attr_name)
377
396
  if child.get_unique_id() in childs:
378
- raise ObjectAlreadyExists(f"Already storing a {child_type} with unique_id {child.get_unique_id()}", child)
397
+ raise ObjectAlreadyExists(
398
+ f"Already storing a {child_type} with unique_id {child.get_unique_id()}",
399
+ child,
400
+ )
379
401
  childs.append(child.get_unique_id())
380
402
 
381
403
  def remove_child(self, child: "DiffSyncModel") -> None:
@@ -402,7 +424,7 @@ class DiffSyncModel(BaseModel):
402
424
  childs.remove(child.get_unique_id())
403
425
 
404
426
 
405
- class DiffSync: # pylint: disable=too-many-public-methods
427
+ class Adapter: # pylint: disable=too-many-public-methods
406
428
  """Class for storing a group of DiffSyncModel instances and diffing/synchronizing to another DiffSync instance."""
407
429
 
408
430
  # In any subclass, you would add mapping of names to specific model classes here:
@@ -416,7 +438,9 @@ class DiffSync: # pylint: disable=too-many-public-methods
416
438
  """List of top-level modelnames to begin from when diffing or synchronizing."""
417
439
 
418
440
  def __init__(
419
- self, name: Optional[str] = None, internal_storage_engine: Union[Type[BaseStore], BaseStore] = LocalStore
441
+ self,
442
+ name: Optional[str] = None,
443
+ internal_storage_engine: Union[Type[BaseStore], BaseStore] = LocalStore,
420
444
  ) -> None:
421
445
  """Generic initialization function.
422
446
 
@@ -425,9 +449,9 @@ class DiffSync: # pylint: disable=too-many-public-methods
425
449
 
426
450
  if isinstance(internal_storage_engine, BaseStore):
427
451
  self.store = internal_storage_engine
428
- self.store.diffsync = self
452
+ self.store.adapter = self
429
453
  else:
430
- self.store = internal_storage_engine(diffsync=self)
454
+ self.store = internal_storage_engine(adapter=self)
431
455
 
432
456
  # If the type is not defined, use the name of the class as the default value
433
457
  if self.type is None:
@@ -535,7 +559,7 @@ class DiffSync: # pylint: disable=too-many-public-methods
535
559
 
536
560
  def sync_from( # pylint: disable=too-many-arguments
537
561
  self,
538
- source: "DiffSync",
562
+ source: "Adapter",
539
563
  diff_class: Type[Diff] = Diff,
540
564
  flags: DiffSyncFlags = DiffSyncFlags.NONE,
541
565
  callback: Optional[Callable[[StrType, int, int], None]] = None,
@@ -564,7 +588,13 @@ class DiffSync: # pylint: disable=too-many-public-methods
564
588
  # Generate the diff if an existing diff was not provided
565
589
  if not diff:
566
590
  diff = self.diff_from(source, diff_class=diff_class, flags=flags, callback=callback)
567
- syncer = DiffSyncSyncer(diff=diff, src_diffsync=source, dst_diffsync=self, flags=flags, callback=callback)
591
+ syncer = DiffSyncSyncer(
592
+ diff=diff,
593
+ src_diffsync=source,
594
+ dst_diffsync=self,
595
+ flags=flags,
596
+ callback=callback,
597
+ )
568
598
  result = syncer.perform_sync()
569
599
  if result:
570
600
  self.sync_complete(source, diff, flags, syncer.base_logger)
@@ -573,7 +603,7 @@ class DiffSync: # pylint: disable=too-many-public-methods
573
603
 
574
604
  def sync_to( # pylint: disable=too-many-arguments
575
605
  self,
576
- target: "DiffSync",
606
+ target: "Adapter",
577
607
  diff_class: Type[Diff] = Diff,
578
608
  flags: DiffSyncFlags = DiffSyncFlags.NONE,
579
609
  callback: Optional[Callable[[StrType, int, int], None]] = None,
@@ -597,7 +627,7 @@ class DiffSync: # pylint: disable=too-many-public-methods
597
627
 
598
628
  def sync_complete(
599
629
  self,
600
- source: "DiffSync",
630
+ source: "Adapter",
601
631
  diff: Diff,
602
632
  flags: DiffSyncFlags = DiffSyncFlags.NONE,
603
633
  logger: Optional[structlog.BoundLogger] = None,
@@ -623,7 +653,7 @@ class DiffSync: # pylint: disable=too-many-public-methods
623
653
 
624
654
  def diff_from(
625
655
  self,
626
- source: "DiffSync",
656
+ source: "Adapter",
627
657
  diff_class: Type[Diff] = Diff,
628
658
  flags: DiffSyncFlags = DiffSyncFlags.NONE,
629
659
  callback: Optional[Callable[[StrType, int, int], None]] = None,
@@ -638,13 +668,17 @@ class DiffSync: # pylint: disable=too-many-public-methods
638
668
  calculation of the diff proceeds.
639
669
  """
640
670
  differ = DiffSyncDiffer(
641
- src_diffsync=source, dst_diffsync=self, flags=flags, diff_class=diff_class, callback=callback
671
+ src_diffsync=source,
672
+ dst_diffsync=self,
673
+ flags=flags,
674
+ diff_class=diff_class,
675
+ callback=callback,
642
676
  )
643
677
  return differ.calculate_diffs()
644
678
 
645
679
  def diff_to(
646
680
  self,
647
- target: "DiffSync",
681
+ target: "Adapter",
648
682
  diff_class: Type[Diff] = Diff,
649
683
  flags: DiffSyncFlags = DiffSyncFlags.NONE,
650
684
  callback: Optional[Callable[[StrType, int, int], None]] = None,
@@ -673,7 +707,9 @@ class DiffSync: # pylint: disable=too-many-public-methods
673
707
  return self.store.get_all_model_names()
674
708
 
675
709
  def get(
676
- self, obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]], identifier: Union[StrType, Dict]
710
+ self,
711
+ obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]],
712
+ identifier: Union[StrType, Dict],
677
713
  ) -> DiffSyncModel:
678
714
  """Get one object from the data store based on its unique id.
679
715
 
@@ -688,7 +724,9 @@ class DiffSync: # pylint: disable=too-many-public-methods
688
724
  return self.store.get(model=obj, identifier=identifier)
689
725
 
690
726
  def get_or_none(
691
- self, obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]], identifier: Union[StrType, Dict]
727
+ self,
728
+ obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]],
729
+ identifier: Union[StrType, Dict],
692
730
  ) -> Optional[DiffSyncModel]:
693
731
  """Get one object from the data store based on its unique id or get a None
694
732
 
@@ -719,7 +757,9 @@ class DiffSync: # pylint: disable=too-many-public-methods
719
757
  return self.store.get_all(model=obj)
720
758
 
721
759
  def get_by_uids(
722
- self, uids: List[StrType], obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]]
760
+ self,
761
+ uids: List[StrType],
762
+ obj: Union[StrType, DiffSyncModel, Type[DiffSyncModel]],
723
763
  ) -> List[DiffSyncModel]:
724
764
  """Get multiple objects from the store by their unique IDs/Keys and type.
725
765
 
@@ -854,5 +894,15 @@ class DiffSync: # pylint: disable=too-many-public-methods
854
894
  return self.store.count(model=model)
855
895
 
856
896
 
857
- # DiffSyncModel references DiffSync and DiffSync references DiffSyncModel. Break the typing loop:
858
- DiffSyncModel.update_forward_refs()
897
+ def DiffSync(*args: Any, **kwargs: Any) -> Adapter: # noqa pylint: disable=invalid-name
898
+ """For backwards-compatibility, keep around the old name."""
899
+
900
+ warnings.warn(
901
+ "'diffsync.DiffSync' is deprecated and will be removed with 2.1, use 'diffsync.Adapter' instead.",
902
+ DeprecationWarning,
903
+ )
904
+ return Adapter(*args, **kwargs)
905
+
906
+
907
+ # DiffSyncModel references Adapter and Adapter references DiffSyncModel. Break the typing loop:
908
+ DiffSyncModel.model_rebuild()
@@ -26,7 +26,7 @@ from .utils import intersection, symmetric_difference
26
26
 
27
27
  if TYPE_CHECKING: # pragma: no cover
28
28
  # For type annotation purposes, we have a circular import loop between __init__.py and this file.
29
- from . import DiffSync, DiffSyncModel # pylint: disable=cyclic-import
29
+ from . import Adapter, DiffSyncModel # pylint: disable=cyclic-import
30
30
 
31
31
 
32
32
  class DiffSyncDiffer: # pylint: disable=too-many-instance-attributes
@@ -37,8 +37,8 @@ class DiffSyncDiffer: # pylint: disable=too-many-instance-attributes
37
37
 
38
38
  def __init__( # pylint: disable=too-many-arguments
39
39
  self,
40
- src_diffsync: "DiffSync",
41
- dst_diffsync: "DiffSync",
40
+ src_diffsync: "Adapter",
41
+ dst_diffsync: "Adapter",
42
42
  flags: DiffSyncFlags,
43
43
  diff_class: Type[Diff] = Diff,
44
44
  callback: Optional[Callable[[str, int, int], None]] = None,
@@ -288,8 +288,8 @@ class DiffSyncSyncer: # pylint: disable=too-many-instance-attributes
288
288
  def __init__( # pylint: disable=too-many-arguments
289
289
  self,
290
290
  diff: Diff,
291
- src_diffsync: "DiffSync",
292
- dst_diffsync: "DiffSync",
291
+ src_diffsync: "Adapter",
292
+ dst_diffsync: "Adapter",
293
293
  flags: DiffSyncFlags,
294
294
  callback: Optional[Callable[[str, int, int], None]] = None,
295
295
  ):
@@ -369,11 +369,13 @@ class DiffSyncSyncer: # pylint: disable=too-many-instance-attributes
369
369
  natural_deletion_order = bool(dst_model.model_flags & DiffSyncModelFlags.NATURAL_DELETION_ORDER)
370
370
  skip_children = bool(dst_model.model_flags & DiffSyncModelFlags.SKIP_CHILDREN_ON_DELETE)
371
371
 
372
+ # Recurse through children to delete if we are supposed to delete the current diff element
372
373
  changed = False
373
374
  if natural_deletion_order and self.action == DiffSyncActions.DELETE and not skip_children:
374
375
  for child in element.get_children():
375
376
  changed |= self.sync_diff_element(child, parent_model=dst_model)
376
377
 
378
+ # Sync the current model - this will delete the current model if self.action is DELETE
377
379
  changed, modified_model = self.sync_model(src_model=src_model, dst_model=dst_model, ids=ids, attrs=attrs)
378
380
  dst_model = modified_model or dst_model
379
381
 
@@ -396,7 +398,7 @@ class DiffSyncSyncer: # pylint: disable=too-many-instance-attributes
396
398
 
397
399
  self.incr_elements_processed()
398
400
 
399
- if not natural_deletion_order:
401
+ if not natural_deletion_order or self.action is not DiffSyncActions.DELETE:
400
402
  for child in element.get_children():
401
403
  changed |= self.sync_diff_element(child, parent_model=dst_model)
402
404
 
@@ -423,7 +425,7 @@ class DiffSyncSyncer: # pylint: disable=too-many-instance-attributes
423
425
  if self.action == DiffSyncActions.CREATE:
424
426
  if dst_model is not None:
425
427
  raise ObjectNotCreated(f"Failed to create {self.model_class.get_type()} {ids} - it already exists!")
426
- dst_model = self.model_class.create(diffsync=self.dst_diffsync, ids=ids, attrs=attrs)
428
+ dst_model = self.model_class.create(adapter=self.dst_diffsync, ids=ids, attrs=attrs)
427
429
  elif self.action == DiffSyncActions.UPDATE:
428
430
  if dst_model is None:
429
431
  raise ObjectNotUpdated(f"Failed to update {self.model_class.get_type()} {ids} - not found!")
@@ -6,7 +6,7 @@ from diffsync.exceptions import ObjectNotFound
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from diffsync import DiffSyncModel
9
- from diffsync import DiffSync
9
+ from diffsync import Adapter
10
10
 
11
11
 
12
12
  class BaseStore:
@@ -15,12 +15,12 @@ class BaseStore:
15
15
  def __init__(
16
16
  self, # pylint: disable=unused-argument
17
17
  *args: Any, # pylint: disable=unused-argument
18
- diffsync: Optional["DiffSync"] = None,
18
+ adapter: Optional["Adapter"] = None,
19
19
  name: str = "",
20
20
  **kwargs: Any, # pylint: disable=unused-argument
21
21
  ) -> None:
22
22
  """Init method for BaseStore."""
23
- self.diffsync = diffsync
23
+ self.adapter = adapter
24
24
  self.name = name or self.__class__.__name__
25
25
  self._log = structlog.get_logger().new(store=self)
26
26
 
@@ -95,8 +95,8 @@ class BaseStore:
95
95
 
96
96
  self.remove_item(modelname, uid)
97
97
 
98
- if obj.diffsync:
99
- obj.diffsync = None
98
+ if obj.adapter:
99
+ obj.adapter = None
100
100
 
101
101
  if remove_children:
102
102
  for child_type, child_fieldname in obj.get_children_mapping().items():
@@ -243,9 +243,9 @@ class BaseStore:
243
243
  """Get object class and model name for a model."""
244
244
  if isinstance(model, str):
245
245
  modelname = model
246
- if not hasattr(self.diffsync, model):
246
+ if not hasattr(self.adapter, model):
247
247
  return None, modelname
248
- object_class = getattr(self.diffsync, model)
248
+ object_class = getattr(self.adapter, model)
249
249
  else:
250
250
  object_class = model
251
251
  modelname = model.get_type()
@@ -108,8 +108,8 @@ class LocalStore(BaseStore):
108
108
  # Return so we don't have to change anything on the existing object and underlying data
109
109
  return
110
110
 
111
- if not obj.diffsync:
112
- obj.diffsync = self.diffsync
111
+ if not obj.adapter:
112
+ obj.adapter = self.adapter
113
113
 
114
114
  self._data[modelname][uid] = obj
115
115
 
@@ -65,7 +65,7 @@ class RedisStore(BaseStore):
65
65
  pickled_object = self._store.get(key)
66
66
  if pickled_object:
67
67
  obj_result = loads(pickled_object) # nosec
68
- obj_result.diffsync = self.diffsync
68
+ obj_result.adapter = self.adapter
69
69
  return obj_result
70
70
  raise ObjectNotFound(f"{key} not present in Cache")
71
71
 
@@ -178,7 +178,7 @@ class RedisStore(BaseStore):
178
178
 
179
179
  # Remove the diffsync object before sending to Redis
180
180
  obj_copy = copy.copy(obj)
181
- obj_copy.diffsync = None
181
+ obj_copy.adapter = None
182
182
 
183
183
  self._store.set(object_key, dumps(obj_copy))
184
184
 
@@ -193,7 +193,7 @@ class RedisStore(BaseStore):
193
193
 
194
194
  object_key = self._get_key_for_object(modelname, uid)
195
195
  obj_copy = copy.copy(obj)
196
- obj_copy.diffsync = None
196
+ obj_copy.adapter = None
197
197
 
198
198
  self._store.set(object_key, dumps(obj_copy))
199
199
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "diffsync"
3
- version = "v1.9.0"
3
+ version = "v2.0.0"
4
4
  description = "Library to easily sync/diff/update 2 different data sources"
5
5
  authors = ["Network to Code, LLC <info@networktocode.com>"]
6
6
  license = "Apache-2.0"
@@ -16,12 +16,14 @@ include = [
16
16
  ]
17
17
 
18
18
  [tool.poetry.dependencies]
19
- python = "^3.7"
20
- pydantic = "^1.7.4,!=1.8,!=1.8.1"
19
+ python = ">=3.8,<4.0"
20
+ pydantic = "^2.0.0"
21
21
  structlog = ">= 20.1.0, < 23.0.0"
22
22
  packaging = ">= 21.3, < 24.0"
23
23
  colorama = {version = "^0.4.3", optional = true}
24
24
  redis = {version = "^4.3", optional = true}
25
+ # typing.Self introduced in 3.11
26
+ typing-extensions = { version = ">=4.0.1", python = "<3.11" }
25
27
 
26
28
  [tool.poetry.extras]
27
29
  redis = ["redis"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes