port-ocean 0.28.9__py3-none-any.whl → 0.28.11__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.
Potentially problematic release.
This version of port-ocean might be problematic. Click here for more details.
- port_ocean/clients/port/mixins/integrations.py +1 -1
- port_ocean/core/handlers/port_app_config/models.py +3 -1
- port_ocean/core/utils/utils.py +16 -5
- port_ocean/tests/core/utils/test_get_port_diff.py +164 -0
- {port_ocean-0.28.9.dist-info → port_ocean-0.28.11.dist-info}/METADATA +1 -1
- {port_ocean-0.28.9.dist-info → port_ocean-0.28.11.dist-info}/RECORD +9 -8
- {port_ocean-0.28.9.dist-info → port_ocean-0.28.11.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.28.9.dist-info → port_ocean-0.28.11.dist-info}/WHEEL +0 -0
- {port_ocean-0.28.9.dist-info → port_ocean-0.28.11.dist-info}/entry_points.txt +0 -0
|
@@ -301,7 +301,7 @@ class IntegrationClientMixin:
|
|
|
301
301
|
headers=headers,
|
|
302
302
|
json={
|
|
303
303
|
"items": raw_data,
|
|
304
|
-
"extractionTimestamp": datetime.now().
|
|
304
|
+
"extractionTimestamp": int(datetime.now().timestamp()),
|
|
305
305
|
},
|
|
306
306
|
)
|
|
307
307
|
handle_port_status_code(response, should_log=False)
|
|
@@ -29,7 +29,9 @@ class EntityMapping(BaseModel):
|
|
|
29
29
|
|
|
30
30
|
@property
|
|
31
31
|
def is_using_search_identifier(self) -> bool:
|
|
32
|
-
return isinstance(self.identifier, dict)
|
|
32
|
+
return isinstance(self.identifier, dict) or isinstance(
|
|
33
|
+
self.identifier, IngestSearchQuery
|
|
34
|
+
)
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
class MappingsConfig(BaseModel):
|
port_ocean/core/utils/utils.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
from typing import Iterable, Any, TypeVar, Callable, Awaitable
|
|
5
5
|
|
|
6
6
|
from loguru import logger
|
|
7
|
-
from pydantic import parse_obj_as, ValidationError
|
|
7
|
+
from pydantic import BaseModel, parse_obj_as, ValidationError
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
from port_ocean.clients.port.client import PortClient
|
|
@@ -79,6 +79,19 @@ async def gather_and_split_errors_from_results(
|
|
|
79
79
|
return valid_items, errors
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def _get_entity_key(entity: Entity) -> tuple[str, str]:
|
|
83
|
+
identifier = entity.identifier
|
|
84
|
+
if isinstance(identifier, BaseModel):
|
|
85
|
+
identifier = identifier.dict()
|
|
86
|
+
|
|
87
|
+
key_part = (
|
|
88
|
+
json.dumps(identifier, sort_keys=True)
|
|
89
|
+
if isinstance(identifier, dict)
|
|
90
|
+
else str(identifier)
|
|
91
|
+
)
|
|
92
|
+
return key_part, entity.blueprint
|
|
93
|
+
|
|
94
|
+
|
|
82
95
|
def get_port_diff(before: Iterable[Entity], after: Iterable[Entity]) -> EntityPortDiff:
|
|
83
96
|
before_dict = {}
|
|
84
97
|
after_dict = {}
|
|
@@ -88,12 +101,10 @@ def get_port_diff(before: Iterable[Entity], after: Iterable[Entity]) -> EntityPo
|
|
|
88
101
|
|
|
89
102
|
# Create dictionaries for before and after lists
|
|
90
103
|
for entity in before:
|
|
91
|
-
|
|
92
|
-
before_dict[key] = entity
|
|
104
|
+
before_dict[_get_entity_key(entity)] = entity
|
|
93
105
|
|
|
94
106
|
for entity in after:
|
|
95
|
-
|
|
96
|
-
after_dict[key] = entity
|
|
107
|
+
after_dict[_get_entity_key(entity)] = entity
|
|
97
108
|
|
|
98
109
|
# Find created, modified, and deleted objects
|
|
99
110
|
for key, obj in after_dict.items():
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from port_ocean.core.models import Entity
|
|
4
|
+
from port_ocean.core.utils.utils import get_port_diff
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_test_entity(
|
|
8
|
+
identifier: str,
|
|
9
|
+
blueprint: str,
|
|
10
|
+
properties: dict[str, Any],
|
|
11
|
+
relations: dict[str, Any],
|
|
12
|
+
title: Any,
|
|
13
|
+
team: str | None | list[Any] = [],
|
|
14
|
+
) -> Entity:
|
|
15
|
+
return Entity(
|
|
16
|
+
identifier=identifier,
|
|
17
|
+
blueprint=blueprint,
|
|
18
|
+
properties=properties,
|
|
19
|
+
relations=relations,
|
|
20
|
+
title=title,
|
|
21
|
+
team=team,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
entity1 = create_test_entity(
|
|
26
|
+
"id1",
|
|
27
|
+
"bp1",
|
|
28
|
+
{"totalIssues": 123, "url": "https://test.atlassian.net/browse/test-29081"},
|
|
29
|
+
{"reporter": "id1", "project": "project_id"},
|
|
30
|
+
"",
|
|
31
|
+
"",
|
|
32
|
+
)
|
|
33
|
+
entity1_modified_properties = create_test_entity(
|
|
34
|
+
"id1",
|
|
35
|
+
"bp1",
|
|
36
|
+
{"totalIssues": 5, "url": "https://test.atlassian.net/browse/test-29081"},
|
|
37
|
+
{"reporter": "id1", "project": "project_id"},
|
|
38
|
+
"",
|
|
39
|
+
"",
|
|
40
|
+
)
|
|
41
|
+
entity2 = create_test_entity(
|
|
42
|
+
"id2",
|
|
43
|
+
"bp2",
|
|
44
|
+
{"totalIssues": 234, "url": "https://test.atlassian.net/browse/test-23451"},
|
|
45
|
+
{"reporter": "id2", "project": "project_id2"},
|
|
46
|
+
"",
|
|
47
|
+
"",
|
|
48
|
+
)
|
|
49
|
+
entity3 = create_test_entity(
|
|
50
|
+
"id3",
|
|
51
|
+
"bp3",
|
|
52
|
+
{"totalIssues": 20, "url": "https://test.atlassian.net/browse/test-542"},
|
|
53
|
+
{"reporter": "id3", "project": "project_id3"},
|
|
54
|
+
"",
|
|
55
|
+
"",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_get_port_diff_with_dictionary_identifier() -> None:
|
|
60
|
+
"""
|
|
61
|
+
Test that get_port_diff handles dictionary identifiers by converting them to strings.
|
|
62
|
+
An entity with a dictionary identifier in 'before' and not in 'after' should be marked as deleted.
|
|
63
|
+
"""
|
|
64
|
+
entity_with_dict_id = create_test_entity(
|
|
65
|
+
identifier={"rules": "some_id", "combinator": "some combinator"}, # type: ignore
|
|
66
|
+
blueprint="bp1",
|
|
67
|
+
properties={},
|
|
68
|
+
relations={},
|
|
69
|
+
title="test",
|
|
70
|
+
)
|
|
71
|
+
before = [entity_with_dict_id]
|
|
72
|
+
after: list[Entity] = []
|
|
73
|
+
|
|
74
|
+
diff = get_port_diff(before, after)
|
|
75
|
+
|
|
76
|
+
assert not diff.created
|
|
77
|
+
assert not diff.modified
|
|
78
|
+
assert len(diff.deleted) == 1
|
|
79
|
+
assert entity_with_dict_id in diff.deleted
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_get_port_diff_no_changes() -> None:
|
|
83
|
+
"""
|
|
84
|
+
Test get_port_diff with no changes between before and after.
|
|
85
|
+
Entities present in both should be in the 'modified' list.
|
|
86
|
+
"""
|
|
87
|
+
before = [entity1, entity2]
|
|
88
|
+
after = [entity1, entity2]
|
|
89
|
+
|
|
90
|
+
diff = get_port_diff(before, after)
|
|
91
|
+
|
|
92
|
+
assert not diff.created
|
|
93
|
+
assert not diff.deleted
|
|
94
|
+
assert len(diff.modified) == 2
|
|
95
|
+
assert entity1 in diff.modified
|
|
96
|
+
assert entity2 in diff.modified
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_get_port_diff_created_entities() -> None:
|
|
100
|
+
"""
|
|
101
|
+
Test get_port_diff with only new entities.
|
|
102
|
+
"""
|
|
103
|
+
before: list[Entity] = []
|
|
104
|
+
after = [entity1, entity2]
|
|
105
|
+
|
|
106
|
+
diff = get_port_diff(before, after)
|
|
107
|
+
|
|
108
|
+
assert not diff.modified
|
|
109
|
+
assert not diff.deleted
|
|
110
|
+
assert len(diff.created) == 2
|
|
111
|
+
assert entity1 in diff.created
|
|
112
|
+
assert entity2 in diff.created
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_get_port_diff_deleted_entities() -> None:
|
|
116
|
+
"""
|
|
117
|
+
Test get_port_diff with only deleted entities.
|
|
118
|
+
"""
|
|
119
|
+
before = [entity1, entity2]
|
|
120
|
+
after: list[Entity] = []
|
|
121
|
+
|
|
122
|
+
diff = get_port_diff(before, after)
|
|
123
|
+
|
|
124
|
+
assert not diff.created
|
|
125
|
+
assert not diff.modified
|
|
126
|
+
assert len(diff.deleted) == 2
|
|
127
|
+
assert entity1 in diff.deleted
|
|
128
|
+
assert entity2 in diff.deleted
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_get_port_diff_modified_entities() -> None:
|
|
132
|
+
"""
|
|
133
|
+
Test get_port_diff with modified entities.
|
|
134
|
+
Entities with same identifier and blueprint are considered modified.
|
|
135
|
+
"""
|
|
136
|
+
before = [entity1, entity2]
|
|
137
|
+
after = [entity1_modified_properties, entity2]
|
|
138
|
+
|
|
139
|
+
diff = get_port_diff(before, after)
|
|
140
|
+
|
|
141
|
+
assert not diff.created
|
|
142
|
+
assert not diff.deleted
|
|
143
|
+
assert len(diff.modified) == 2
|
|
144
|
+
assert entity1_modified_properties in diff.modified
|
|
145
|
+
assert entity2 in diff.modified
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_get_port_diff_mixed_changes() -> None:
|
|
149
|
+
"""
|
|
150
|
+
Test get_port_diff with a mix of created, modified, and deleted entities.
|
|
151
|
+
"""
|
|
152
|
+
before = [entity1, entity2]
|
|
153
|
+
after = [entity1_modified_properties, entity3]
|
|
154
|
+
|
|
155
|
+
diff = get_port_diff(before, after)
|
|
156
|
+
|
|
157
|
+
assert len(diff.created) == 1
|
|
158
|
+
assert entity3 in diff.created
|
|
159
|
+
|
|
160
|
+
assert len(diff.modified) == 1
|
|
161
|
+
assert entity1_modified_properties in diff.modified
|
|
162
|
+
|
|
163
|
+
assert len(diff.deleted) == 1
|
|
164
|
+
assert entity2 in diff.deleted
|
|
@@ -61,7 +61,7 @@ port_ocean/clients/port/client.py,sha256=hBXgU0CDseN2F-vn20JqowfVkcd6oSVmYrjn6t4
|
|
|
61
61
|
port_ocean/clients/port/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
62
|
port_ocean/clients/port/mixins/blueprints.py,sha256=aMCG4zePsMSMjMLiGrU37h5z5_ElfMzTcTvqvOI5wXY,4683
|
|
63
63
|
port_ocean/clients/port/mixins/entities.py,sha256=X2NqH00eK6TMJ3a3QEQRVQlKHzyj5l1FiPkIhonnbPg,24234
|
|
64
|
-
port_ocean/clients/port/mixins/integrations.py,sha256=
|
|
64
|
+
port_ocean/clients/port/mixins/integrations.py,sha256=oBkO6OtRtos7KmjS-QUnudhrxHRoNOmaClVNnc1_EHU,12042
|
|
65
65
|
port_ocean/clients/port/mixins/migrations.py,sha256=vdL_A_NNUogvzujyaRLIoZEu5vmKDY2BxTjoGP94YzI,1467
|
|
66
66
|
port_ocean/clients/port/mixins/organization.py,sha256=A2cP5V49KnjoAXxjmnm_XGth4ftPSU0qURNfnyUyS_Y,1041
|
|
67
67
|
port_ocean/clients/port/retry_transport.py,sha256=PtIZOAZ6V-ncpVysRUsPOgt8Sf01QLnTKB5YeKBxkJk,1861
|
|
@@ -105,7 +105,7 @@ port_ocean/core/handlers/entity_processor/jq_entity_processor.py,sha256=qvPMbIH1
|
|
|
105
105
|
port_ocean/core/handlers/port_app_config/__init__.py,sha256=8AAT5OthiVM7KCcM34iEgEeXtn2pRMrT4Dze5r1Ixbk,134
|
|
106
106
|
port_ocean/core/handlers/port_app_config/api.py,sha256=r_Th66NEw38IpRdnXZcRvI8ACfvxW_A6V62WLwjWXlQ,1044
|
|
107
107
|
port_ocean/core/handlers/port_app_config/base.py,sha256=Sup4-X_a7JGa27rMy_OgqGIjFHMlKBpKevicaK3AeHU,2919
|
|
108
|
-
port_ocean/core/handlers/port_app_config/models.py,sha256=
|
|
108
|
+
port_ocean/core/handlers/port_app_config/models.py,sha256=J-dfd9fEcTA77zJKL_Qd3bvg-v3B_-vZe06Mo-V5ptE,3091
|
|
109
109
|
port_ocean/core/handlers/queue/__init__.py,sha256=yzgicE_jAR1wtljFKxgyG6j-HbLcG_Zze5qw1kkALUI,171
|
|
110
110
|
port_ocean/core/handlers/queue/abstract_queue.py,sha256=SaivrYbqg8qsX6wtQlJZyxgcbdMD5B9NZG3byN9AvrI,782
|
|
111
111
|
port_ocean/core/handlers/queue/group_queue.py,sha256=JvvJOwz9z_aI4CjPr7yQX-0rOgqLI5wMdxWk2x5x-34,4989
|
|
@@ -128,7 +128,7 @@ port_ocean/core/integrations/mixins/utils.py,sha256=ytnFX7Lyv6N3CgBnOXxYaI1cRDq5
|
|
|
128
128
|
port_ocean/core/models.py,sha256=DNbKpStMINI2lIekKprTqBevqkw_wFuFayN19w1aDfQ,2893
|
|
129
129
|
port_ocean/core/ocean_types.py,sha256=bkLlTd8XfJK6_JDl0eXUHfE_NygqgiInSMwJ4YJH01Q,1399
|
|
130
130
|
port_ocean/core/utils/entity_topological_sorter.py,sha256=MDUjM6OuDy4Xj68o-7InNN0w1jqjxeDfeY8U02vySNI,3081
|
|
131
|
-
port_ocean/core/utils/utils.py,sha256=
|
|
131
|
+
port_ocean/core/utils/utils.py,sha256=6ySxua6JgVxcjESPL5MScdkpaUj5XR9srorGHHb0B2o,7157
|
|
132
132
|
port_ocean/debug_cli.py,sha256=gHrv-Ey3cImKOcGZpjoHlo4pa_zfmyOl6TUM4o9VtcA,96
|
|
133
133
|
port_ocean/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
134
134
|
port_ocean/exceptions/api.py,sha256=1JcA-H12lhSgolMEA6dM4JMbDrh9sYDcE7oydPSTuK8,649
|
|
@@ -182,6 +182,7 @@ port_ocean/tests/core/handlers/webhook/test_processor_manager.py,sha256=16q-5Nag
|
|
|
182
182
|
port_ocean/tests/core/handlers/webhook/test_webhook_event.py,sha256=oR4dEHLO65mp6rkfNfszZcfFoRZlB8ZWee4XetmsuIk,3181
|
|
183
183
|
port_ocean/tests/core/test_utils.py,sha256=Z3kdhb5V7Svhcyy3EansdTpgHL36TL6erNtU-OPwAcI,2647
|
|
184
184
|
port_ocean/tests/core/utils/test_entity_topological_sorter.py,sha256=zuq5WSPy_88PemG3mOUIHTxWMR_js1R7tOzUYlgBd68,3447
|
|
185
|
+
port_ocean/tests/core/utils/test_get_port_diff.py,sha256=YoQxAHZdX5nVpvrKV5Aox-jQ4w14AbfJVo-QK-ICAb8,4297
|
|
185
186
|
port_ocean/tests/core/utils/test_resolve_entities_diff.py,sha256=nSB0H87Gk6iFw7RMq9YfiZtC_u6X20ao5vmvywP5HsE,17945
|
|
186
187
|
port_ocean/tests/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
187
188
|
port_ocean/tests/helpers/fake_port_api.py,sha256=9rtjC6iTQMfzWK6WipkDzzG0b1IIaRmvdJLOyV613vE,6479
|
|
@@ -208,8 +209,8 @@ port_ocean/utils/repeat.py,sha256=U2OeCkHPWXmRTVoPV-VcJRlQhcYqPWI5NfmPlb1JIbc,32
|
|
|
208
209
|
port_ocean/utils/signal.py,sha256=J1sI-e_32VHP_VUa5bskLMFoJjJOAk5isrnewKDikUI,2125
|
|
209
210
|
port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
|
|
210
211
|
port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
|
|
211
|
-
port_ocean-0.28.
|
|
212
|
-
port_ocean-0.28.
|
|
213
|
-
port_ocean-0.28.
|
|
214
|
-
port_ocean-0.28.
|
|
215
|
-
port_ocean-0.28.
|
|
212
|
+
port_ocean-0.28.11.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
213
|
+
port_ocean-0.28.11.dist-info/METADATA,sha256=_DEW9DiFQmGDhG8llM9ln0bH4Bmg_rurx0SQ_FQSMPI,7016
|
|
214
|
+
port_ocean-0.28.11.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
215
|
+
port_ocean-0.28.11.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
|
|
216
|
+
port_ocean-0.28.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|