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.

@@ -301,7 +301,7 @@ class IntegrationClientMixin:
301
301
  headers=headers,
302
302
  json={
303
303
  "items": raw_data,
304
- "extractionTimestamp": datetime.now().isoformat(),
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):
@@ -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
- key = (entity.identifier, entity.blueprint)
92
- before_dict[key] = entity
104
+ before_dict[_get_entity_key(entity)] = entity
93
105
 
94
106
  for entity in after:
95
- key = (entity.identifier, entity.blueprint)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.28.9
3
+ Version: 0.28.11
4
4
  Summary: Port Ocean is a CLI tool for managing your Port projects.
5
5
  Home-page: https://app.getport.io
6
6
  Keywords: ocean,port-ocean,port
@@ -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=16RGnrEnToSkOgffUN1Q62J81Oo_8acvw-cxztuy5Ys,12037
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=M9NJRRacvpZQYCFgHGlRwrdgumbIIJ82WpCyvtOUf5w,3019
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=XJ6ZZBR5hols19TcX4Bh49ygSNhPt3MLncLR-g41GTA,6858
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.9.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
212
- port_ocean-0.28.9.dist-info/METADATA,sha256=gd8ZvFlVK5p1ypLtnTiCIJwvdoMtXm4Tli18ebscTjs,7015
213
- port_ocean-0.28.9.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
214
- port_ocean-0.28.9.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
215
- port_ocean-0.28.9.dist-info/RECORD,,
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,,