port-ocean 0.5.17__py3-none-any.whl → 0.5.19__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/config/settings.py +1 -0
- port_ocean/core/handlers/entity_processor/jq_entity_processor.py +11 -14
- port_ocean/core/handlers/port_app_config/base.py +53 -15
- port_ocean/core/integrations/mixins/sync_raw.py +5 -1
- port_ocean/core/utils.py +27 -31
- port_ocean/utils/queue_utils.py +81 -0
- {port_ocean-0.5.17.dist-info → port_ocean-0.5.19.dist-info}/METADATA +1 -1
- {port_ocean-0.5.17.dist-info → port_ocean-0.5.19.dist-info}/RECORD +11 -10
- {port_ocean-0.5.17.dist-info → port_ocean-0.5.19.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.5.17.dist-info → port_ocean-0.5.19.dist-info}/WHEEL +0 -0
- {port_ocean-0.5.17.dist-info → port_ocean-0.5.19.dist-info}/entry_points.txt +0 -0
port_ocean/config/settings.py
CHANGED
|
@@ -36,6 +36,7 @@ class PortSettings(BaseOceanModel, extra=Extra.allow):
|
|
|
36
36
|
client_id: str = Field(..., sensitive=True)
|
|
37
37
|
client_secret: str = Field(..., sensitive=True)
|
|
38
38
|
base_url: AnyHttpUrl = parse_obj_as(AnyHttpUrl, "https://api.getport.io")
|
|
39
|
+
port_app_config_cache_ttl: int = 60
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class IntegrationSettings(BaseOceanModel, extra=Extra.allow):
|
|
@@ -14,6 +14,7 @@ from port_ocean.core.ocean_types import (
|
|
|
14
14
|
EntitySelectorDiff,
|
|
15
15
|
)
|
|
16
16
|
from port_ocean.exceptions.core import EntityProcessorException
|
|
17
|
+
from port_ocean.utils.queue_utils import process_in_queue
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class JQEntityProcessor(BaseEntityProcessor):
|
|
@@ -128,23 +129,19 @@ class JQEntityProcessor(BaseEntityProcessor):
|
|
|
128
129
|
raw_entity_mappings: dict[str, Any] = mapping.port.entity.mappings.dict(
|
|
129
130
|
exclude_unset=True
|
|
130
131
|
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
for data in raw_results
|
|
142
|
-
]
|
|
143
|
-
calculate_entities_results = await asyncio.gather(*calculate_entities_tasks)
|
|
132
|
+
|
|
133
|
+
calculated_entities_results = await process_in_queue(
|
|
134
|
+
raw_results,
|
|
135
|
+
self._calculate_entity,
|
|
136
|
+
raw_entity_mappings,
|
|
137
|
+
mapping.port.items_to_parse,
|
|
138
|
+
mapping.selector.query,
|
|
139
|
+
parse_all,
|
|
140
|
+
)
|
|
144
141
|
|
|
145
142
|
passed_entities = []
|
|
146
143
|
failed_entities = []
|
|
147
|
-
for entities_results in
|
|
144
|
+
for entities_results in calculated_entities_results:
|
|
148
145
|
for entity, did_entity_pass_selector in entities_results:
|
|
149
146
|
if entity.get("identifier") and entity.get("blueprint"):
|
|
150
147
|
parsed_entity = Entity.parse_obj(entity)
|
|
@@ -5,8 +5,36 @@ from loguru import logger
|
|
|
5
5
|
from pydantic import ValidationError
|
|
6
6
|
|
|
7
7
|
from port_ocean.context.event import event
|
|
8
|
+
from port_ocean.context.ocean import PortOceanContext
|
|
8
9
|
from port_ocean.core.handlers.base import BaseHandler
|
|
9
10
|
from port_ocean.core.handlers.port_app_config.models import PortAppConfig
|
|
11
|
+
from port_ocean.utils.misc import get_time
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PortAppConfigCache:
|
|
15
|
+
_port_app_config: PortAppConfig | None
|
|
16
|
+
_retrieval_time: float
|
|
17
|
+
|
|
18
|
+
def __init__(self, cache_ttl: int):
|
|
19
|
+
self._cache_ttl = cache_ttl
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def port_app_config(self) -> PortAppConfig:
|
|
23
|
+
if self._port_app_config is None:
|
|
24
|
+
raise ValueError("Port app config is not set")
|
|
25
|
+
return self._port_app_config
|
|
26
|
+
|
|
27
|
+
@port_app_config.setter
|
|
28
|
+
def port_app_config(self, value: PortAppConfig) -> None:
|
|
29
|
+
self._retrieval_time = get_time()
|
|
30
|
+
self._port_app_config = value
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def is_cache_invalid(self) -> bool:
|
|
34
|
+
return (
|
|
35
|
+
not self._port_app_config
|
|
36
|
+
or self._retrieval_time + self._cache_ttl < get_time()
|
|
37
|
+
)
|
|
10
38
|
|
|
11
39
|
|
|
12
40
|
class BasePortAppConfig(BaseHandler):
|
|
@@ -21,24 +49,34 @@ class BasePortAppConfig(BaseHandler):
|
|
|
21
49
|
|
|
22
50
|
CONFIG_CLASS: Type[PortAppConfig] = PortAppConfig
|
|
23
51
|
|
|
52
|
+
def __init__(self, context: PortOceanContext):
|
|
53
|
+
super().__init__(context)
|
|
54
|
+
self._app_config_cache = PortAppConfigCache(
|
|
55
|
+
self.context.config.port.port_app_config_cache_ttl
|
|
56
|
+
)
|
|
57
|
+
|
|
24
58
|
@abstractmethod
|
|
25
59
|
async def _get_port_app_config(self) -> dict[str, Any]:
|
|
26
60
|
pass
|
|
27
61
|
|
|
28
|
-
async def get_port_app_config(self) -> PortAppConfig:
|
|
29
|
-
"""
|
|
62
|
+
async def get_port_app_config(self, use_cache: bool = True) -> PortAppConfig:
|
|
63
|
+
"""
|
|
64
|
+
Retrieve and parse the port application configuration.
|
|
30
65
|
|
|
31
|
-
|
|
32
|
-
|
|
66
|
+
:param use_cache: Determines whether to use the cached port-app-config if it exists, or to fetch it regardless
|
|
67
|
+
:return: The parsed port application configuration.
|
|
33
68
|
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
69
|
+
if not use_cache or self._app_config_cache.is_cache_invalid:
|
|
70
|
+
raw_config = await self._get_port_app_config()
|
|
71
|
+
try:
|
|
72
|
+
self._app_config_cache.port_app_config = self.CONFIG_CLASS.parse_obj(
|
|
73
|
+
raw_config
|
|
74
|
+
)
|
|
75
|
+
except ValidationError:
|
|
76
|
+
logger.error(
|
|
77
|
+
"Invalid port app config found. Please check that the integration has been configured correctly."
|
|
78
|
+
)
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
event.port_app_config = self._app_config_cache.port_app_config
|
|
82
|
+
return self._app_config_cache.port_app_config
|
|
@@ -362,7 +362,11 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
362
362
|
EventType.RESYNC,
|
|
363
363
|
trigger_type=trigger_type,
|
|
364
364
|
):
|
|
365
|
-
|
|
365
|
+
# If a resync is triggered due to a mappings change, we want to make sure that we have the updated version
|
|
366
|
+
# rather than the old cache
|
|
367
|
+
app_config = await self.port_app_config_handler.get_port_app_config(
|
|
368
|
+
use_cache=False
|
|
369
|
+
)
|
|
366
370
|
|
|
367
371
|
creation_results: list[tuple[list[Entity], list[Exception]]] = []
|
|
368
372
|
|
port_ocean/core/utils.py
CHANGED
|
@@ -28,38 +28,34 @@ def is_same_entity(first_entity: Entity, second_entity: Entity) -> bool:
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def get_unique(array: list[Entity]) -> list[Entity]:
|
|
32
|
-
result: list[Entity] = []
|
|
33
|
-
for item in array:
|
|
34
|
-
if all(not is_same_entity(item, seen_item) for seen_item in result):
|
|
35
|
-
result.append(item)
|
|
36
|
-
return result
|
|
37
|
-
|
|
38
|
-
|
|
39
31
|
def get_port_diff(
|
|
40
32
|
before: Iterable[Entity],
|
|
41
33
|
after: Iterable[Entity],
|
|
42
34
|
) -> EntityPortDiff:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
35
|
+
before_dict = {}
|
|
36
|
+
after_dict = {}
|
|
37
|
+
created = []
|
|
38
|
+
modified = []
|
|
39
|
+
deleted = []
|
|
40
|
+
|
|
41
|
+
# Create dictionaries for before and after lists
|
|
42
|
+
for entity in before:
|
|
43
|
+
key = (entity.identifier, entity.blueprint)
|
|
44
|
+
before_dict[key] = entity
|
|
45
|
+
|
|
46
|
+
for entity in after:
|
|
47
|
+
key = (entity.identifier, entity.blueprint)
|
|
48
|
+
after_dict[key] = entity
|
|
49
|
+
|
|
50
|
+
# Find created, modified, and deleted objects
|
|
51
|
+
for key, obj in after_dict.items():
|
|
52
|
+
if key not in before_dict:
|
|
53
|
+
created.append(obj)
|
|
54
|
+
else:
|
|
55
|
+
modified.append(obj)
|
|
56
|
+
|
|
57
|
+
for key, obj in before_dict.items():
|
|
58
|
+
if key not in after_dict:
|
|
59
|
+
deleted.append(obj)
|
|
60
|
+
|
|
61
|
+
return EntityPortDiff(created=created, modified=modified, deleted=deleted)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from asyncio import Queue, Task
|
|
3
|
+
from typing import Any, TypeVar, Callable, Coroutine
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def _start_processor_worker(
|
|
11
|
+
queue: Queue[Any | None],
|
|
12
|
+
func: Callable[..., Coroutine[Any, Any, T]],
|
|
13
|
+
results: list[T],
|
|
14
|
+
) -> None:
|
|
15
|
+
while True:
|
|
16
|
+
raw_params = await queue.get()
|
|
17
|
+
try:
|
|
18
|
+
if raw_params is None:
|
|
19
|
+
return
|
|
20
|
+
logger.debug(f"Processing {raw_params[0]}")
|
|
21
|
+
results.append(await func(*raw_params))
|
|
22
|
+
finally:
|
|
23
|
+
queue.task_done()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async def process_in_queue(
|
|
27
|
+
objects_to_process: list[Any],
|
|
28
|
+
func: Callable[..., Coroutine[Any, Any, T]],
|
|
29
|
+
*args: Any,
|
|
30
|
+
concurrency: int = 50,
|
|
31
|
+
) -> list[T]:
|
|
32
|
+
"""
|
|
33
|
+
This function executes multiple asynchronous tasks in a bounded way
|
|
34
|
+
(e.g. having 200 tasks to execute, while running only 20 concurrently),
|
|
35
|
+
to prevent overload and memory issues when dealing with large sets of data and tasks.
|
|
36
|
+
read more -> https://stackoverflow.com/questions/38831322/making-1-milion-requests-with-aiohttp-asyncio-literally
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
```python
|
|
40
|
+
async def incrementBy(num: int, increment_by: int) -> int:
|
|
41
|
+
await asyncio.sleep(3)
|
|
42
|
+
return num + increment_by
|
|
43
|
+
|
|
44
|
+
async def main():
|
|
45
|
+
raw_objects = [1, 2, 3, 4, 5]
|
|
46
|
+
processed_objects = await process_in_queue(
|
|
47
|
+
raw_objects,
|
|
48
|
+
incrementBy,
|
|
49
|
+
5
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
:param objects_to_process: A list of the raw objects to process
|
|
54
|
+
:param func: An async function that turns raw object into result object
|
|
55
|
+
:param args: Static arguments to pass to the func when called
|
|
56
|
+
:param concurrency: An integer specifying the concurrent workers count
|
|
57
|
+
:return: A list of result objects
|
|
58
|
+
"""
|
|
59
|
+
queue: Queue[Any | None] = Queue(maxsize=concurrency * 2)
|
|
60
|
+
tasks: list[Task[Any]] = []
|
|
61
|
+
processing_results: list[T] = []
|
|
62
|
+
|
|
63
|
+
for i in range(concurrency):
|
|
64
|
+
tasks.append(
|
|
65
|
+
asyncio.create_task(
|
|
66
|
+
_start_processor_worker(queue, func, processing_results)
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
for i in range(len(objects_to_process)):
|
|
71
|
+
await queue.put((objects_to_process[i], *args))
|
|
72
|
+
|
|
73
|
+
for i in range(concurrency):
|
|
74
|
+
# We put None value into the queue, so the workers will know that we
|
|
75
|
+
# are done sending more input and they can terminate
|
|
76
|
+
await queue.put(None)
|
|
77
|
+
|
|
78
|
+
await queue.join()
|
|
79
|
+
await asyncio.gather(*tasks)
|
|
80
|
+
|
|
81
|
+
return processing_results
|
|
@@ -49,7 +49,7 @@ port_ocean/clients/port/utils.py,sha256=O9mBu6zp4TfpS4SQ3qCPpn9ZVyYF8GKnji4UnYhM
|
|
|
49
49
|
port_ocean/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
50
|
port_ocean/config/base.py,sha256=8JoyjTPJS_BeYMnGnKN_q4SDaI-ydctuKl9ccK-Uq78,6474
|
|
51
51
|
port_ocean/config/dynamic.py,sha256=qOFkRoJsn_BW7581omi_AoMxoHqasf_foxDQ_G11_SI,2030
|
|
52
|
-
port_ocean/config/settings.py,sha256=
|
|
52
|
+
port_ocean/config/settings.py,sha256=7eJSuOQU69NWrxUfy6UnSdL9aZQ_nu9VOp0-XHy9-Ds,1809
|
|
53
53
|
port_ocean/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
54
|
port_ocean/consumers/kafka_consumer.py,sha256=N8KocjBi9aR0BOPG8hgKovg-ns_ggpEjrSxqSqF_BSo,4710
|
|
55
55
|
port_ocean/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -78,10 +78,10 @@ port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py,sha
|
|
|
78
78
|
port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py,sha256=82BvU8t5w9uhsxX8hbnwuRPuWhW3cMeuT_5sVIkip1I,1550
|
|
79
79
|
port_ocean/core/handlers/entity_processor/__init__.py,sha256=FvFCunFg44wNQoqlybem9MthOs7p1Wawac87uSXz9U8,156
|
|
80
80
|
port_ocean/core/handlers/entity_processor/base.py,sha256=4JVCAAohEKtl8FdlnuyIxJ1afSXk3o2-e_m4LSy7vmw,1952
|
|
81
|
-
port_ocean/core/handlers/entity_processor/jq_entity_processor.py,sha256=
|
|
81
|
+
port_ocean/core/handlers/entity_processor/jq_entity_processor.py,sha256=FDpMeBntu5x-p2DGuk8BpQVe1zy7afJ93nWfrLnZPRc,5479
|
|
82
82
|
port_ocean/core/handlers/port_app_config/__init__.py,sha256=8AAT5OthiVM7KCcM34iEgEeXtn2pRMrT4Dze5r1Ixbk,134
|
|
83
83
|
port_ocean/core/handlers/port_app_config/api.py,sha256=6VbKPwFzsWG0IYsVD81hxSmfqtHUFqrfUuj1DBX5g4w,853
|
|
84
|
-
port_ocean/core/handlers/port_app_config/base.py,sha256=
|
|
84
|
+
port_ocean/core/handlers/port_app_config/base.py,sha256=oufdNLzUmEgJY5PgIz75zDlowWrjA-y8WR4UnM58E58,2897
|
|
85
85
|
port_ocean/core/handlers/port_app_config/models.py,sha256=WtF2uW4KPUPfDpy6E2tl9qXh5GUcDucVvKt0DCTYv6w,1985
|
|
86
86
|
port_ocean/core/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
87
87
|
port_ocean/core/integrations/base.py,sha256=3jU0skK_gMLB8a_fN8whsRKva-Dz058TFoI0vXTbryU,3193
|
|
@@ -89,11 +89,11 @@ port_ocean/core/integrations/mixins/__init__.py,sha256=FA1FEKMM6P-L2_m7Q4L20mFa4
|
|
|
89
89
|
port_ocean/core/integrations/mixins/events.py,sha256=Ddfx2L4FpghV38waF8OfVeOV0bHBxNIgjU-q5ffillI,2341
|
|
90
90
|
port_ocean/core/integrations/mixins/handler.py,sha256=mZ7-0UlG3LcrwJttFbMe-R4xcOU2H_g33tZar7PwTv8,3771
|
|
91
91
|
port_ocean/core/integrations/mixins/sync.py,sha256=TKqRytxXONVhuCo3CB3rDvWNbITnZz33TYTDs3SWWVk,3880
|
|
92
|
-
port_ocean/core/integrations/mixins/sync_raw.py,sha256=
|
|
92
|
+
port_ocean/core/integrations/mixins/sync_raw.py,sha256=iWI8fXd36TwUNFDAgEJj4SGvWwDOQsez1sQpc2EriKQ,15253
|
|
93
93
|
port_ocean/core/integrations/mixins/utils.py,sha256=7y1rGETZIjOQadyIjFJXIHKkQFKx_SwiP-TrAIsyyLY,2303
|
|
94
94
|
port_ocean/core/models.py,sha256=bDO_I4Yd33TEZIh2QSV8UwXQIuwE7IgrINkYDHI0dkc,714
|
|
95
95
|
port_ocean/core/ocean_types.py,sha256=ltnn22eRuDMFW02kIgmIAu6S06-i9jJV2NJ-MZcwwj0,879
|
|
96
|
-
port_ocean/core/utils.py,sha256=
|
|
96
|
+
port_ocean/core/utils.py,sha256=6rYtsb1bjW8owxWngGiV3awMLZkP3tXZdxXClmRD1SU,1824
|
|
97
97
|
port_ocean/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
98
|
port_ocean/exceptions/api.py,sha256=TLmTMqn4uHGaHgZK8PMIJ0TVJlPB4iP7xl9rx7GtCyY,426
|
|
99
99
|
port_ocean/exceptions/base.py,sha256=uY4DX7fIITDFfemCJDWpaZi3bD51lcANc5swpoNvMJA,46
|
|
@@ -119,11 +119,12 @@ port_ocean/utils/async_http.py,sha256=arnH458TExn2Dju_Sy6pHas_vF5RMWnOp-jBz5WAAc
|
|
|
119
119
|
port_ocean/utils/async_iterators.py,sha256=buFBiPdsqkNMCk91h6ZG8hJa181j7RjgHajbfgeB8A8,1608
|
|
120
120
|
port_ocean/utils/cache.py,sha256=3KItZDE2yVrbVDr-hoM8lNna8s2dlpxhP4ICdLjH4LQ,2231
|
|
121
121
|
port_ocean/utils/misc.py,sha256=2XmO8W0SgPjV0rd9HZvrHhoMlHprIwmMFsINxlAmgyw,1723
|
|
122
|
+
port_ocean/utils/queue_utils.py,sha256=Pzb6e8PcjylZpXcb9EEIK-QcTty_E2k1egMiJF5J_8Q,2500
|
|
122
123
|
port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,3231
|
|
123
124
|
port_ocean/utils/signal.py,sha256=Fab0049Cjs69TPTQgvEvilaVZKACQr6tGkRdySjNCi8,1515
|
|
124
125
|
port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
|
|
125
|
-
port_ocean-0.5.
|
|
126
|
-
port_ocean-0.5.
|
|
127
|
-
port_ocean-0.5.
|
|
128
|
-
port_ocean-0.5.
|
|
129
|
-
port_ocean-0.5.
|
|
126
|
+
port_ocean-0.5.19.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
127
|
+
port_ocean-0.5.19.dist-info/METADATA,sha256=m3voj8mZP19CPHPXnBDLx58G07P5kfxDuCWAjvu2wTE,6554
|
|
128
|
+
port_ocean-0.5.19.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
129
|
+
port_ocean-0.5.19.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
|
|
130
|
+
port_ocean-0.5.19.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|