port-ocean 0.1.3rc4__py3-none-any.whl → 0.1.4.dev1__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/cli/commands/new.py +6 -1
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +1 -1
- port_ocean/config/base.py +1 -1
- port_ocean/core/event_listener/base.py +7 -0
- port_ocean/core/event_listener/factory.py +17 -0
- port_ocean/core/event_listener/http/__init__.py +0 -0
- port_ocean/core/event_listener/http/event_listener.py +23 -0
- port_ocean/core/event_listener/kafka/__init__.py +0 -0
- port_ocean/core/event_listener/kafka/event_listener.py +51 -4
- port_ocean/core/event_listener/polling/event_listener.py +22 -0
- port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +11 -4
- port_ocean/core/integrations/base.py +26 -0
- port_ocean/core/integrations/mixins/utils.py +4 -3
- port_ocean/core/models.py +2 -14
- port_ocean/logger_setup.py +14 -0
- {port_ocean-0.1.3rc4.dist-info → port_ocean-0.1.4.dev1.dist-info}/METADATA +1 -1
- {port_ocean-0.1.3rc4.dist-info → port_ocean-0.1.4.dev1.dist-info}/RECORD +20 -18
- {port_ocean-0.1.3rc4.dist-info → port_ocean-0.1.4.dev1.dist-info}/LICENSE +0 -0
- {port_ocean-0.1.3rc4.dist-info → port_ocean-0.1.4.dev1.dist-info}/WHEEL +0 -0
- {port_ocean-0.1.3rc4.dist-info → port_ocean-0.1.4.dev1.dist-info}/entry_points.txt +0 -0
port_ocean/cli/commands/new.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import click
|
|
4
4
|
from cookiecutter.main import cookiecutter # type: ignore
|
|
5
5
|
|
|
6
|
+
from port_ocean import __version__
|
|
6
7
|
from port_ocean.cli.commands.main import cli_start, print_logo, console
|
|
7
8
|
from port_ocean.cli.utils import cli_root_path
|
|
8
9
|
|
|
@@ -21,7 +22,11 @@ def new(path: str) -> None:
|
|
|
21
22
|
"🚢 Unloading cargo... Setting up your integration at the dock.", style="bold"
|
|
22
23
|
)
|
|
23
24
|
|
|
24
|
-
result = cookiecutter(
|
|
25
|
+
result = cookiecutter(
|
|
26
|
+
f"{cli_root_path}/cookiecutter",
|
|
27
|
+
output_dir=path,
|
|
28
|
+
extra_context={"version": __version__},
|
|
29
|
+
)
|
|
25
30
|
name = result.split("/")[-1]
|
|
26
31
|
|
|
27
32
|
console.print(
|
|
@@ -6,7 +6,7 @@ authors = ["{{cookiecutter.full_name}} <{{cookiecutter.email}}>"]
|
|
|
6
6
|
|
|
7
7
|
[tool.poetry.dependencies]
|
|
8
8
|
python = "^3.11"
|
|
9
|
-
port_ocean = { version = "^
|
|
9
|
+
port_ocean = { version = "^{{ cookiecutter.version }}", extras = ["cli"] }
|
|
10
10
|
|
|
11
11
|
[tool.poetry.group.dev.dependencies]
|
|
12
12
|
pytest = "^7.2"
|
port_ocean/config/base.py
CHANGED
|
@@ -8,7 +8,7 @@ from humps import decamelize
|
|
|
8
8
|
from pydantic import BaseSettings
|
|
9
9
|
from pydantic.env_settings import EnvSettingsSource, InitSettingsSource
|
|
10
10
|
|
|
11
|
-
PROVIDER_WRAPPER_PATTERN = r"
|
|
11
|
+
PROVIDER_WRAPPER_PATTERN = r"{{ from (.*) }}"
|
|
12
12
|
PROVIDER_CONFIG_PATTERN = r"^[a-zA-Z0-9]+ .*$"
|
|
13
13
|
|
|
14
14
|
|
|
@@ -5,6 +5,9 @@ from pydantic import BaseModel, Extra
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class EventListenerEvents(TypedDict):
|
|
8
|
+
"""
|
|
9
|
+
A dictionary containing event types and their corresponding event handlers.
|
|
10
|
+
"""
|
|
8
11
|
on_resync: Callable[[dict[Any, Any]], Awaitable[None]]
|
|
9
12
|
|
|
10
13
|
|
|
@@ -24,4 +27,8 @@ class EventListenerSettings(BaseModel, extra=Extra.allow):
|
|
|
24
27
|
type: str
|
|
25
28
|
|
|
26
29
|
def to_request(self) -> dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Converts the Settings object to a dictionary representation (request format).
|
|
32
|
+
This method is used when configuring the event listener settings for the Polling event listener.
|
|
33
|
+
"""
|
|
27
34
|
return {"type": self.type}
|
|
@@ -20,6 +20,16 @@ from port_ocean.exceptions.core import UnsupportedEventListenerTypeException
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class EventListenerFactory(BaseWithContext):
|
|
23
|
+
"""
|
|
24
|
+
This class is responsible for creating different types of event listeners based on the provided configuration.
|
|
25
|
+
The factory takes a PortOceanContext, installation_id, and events dictionary as parameters.
|
|
26
|
+
|
|
27
|
+
Parameters:
|
|
28
|
+
context (PortOceanContext): The PortOceanContext object containing information about the current application context.
|
|
29
|
+
installation_id (str): The identifier of the installation associated with the event listener.
|
|
30
|
+
events (EventListenerEvents): A dictionary containing event types and their corresponding event handlers.
|
|
31
|
+
|
|
32
|
+
"""
|
|
23
33
|
def __init__(
|
|
24
34
|
self,
|
|
25
35
|
context: PortOceanContext,
|
|
@@ -31,6 +41,13 @@ class EventListenerFactory(BaseWithContext):
|
|
|
31
41
|
self.events = events
|
|
32
42
|
|
|
33
43
|
async def create_event_listener(self) -> BaseEventListener:
|
|
44
|
+
"""
|
|
45
|
+
Creates and returns a specific event listener based on the provided configuration.
|
|
46
|
+
The event listener is wrapped with the events provided in the factory's initialization.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
UnsupportedEventListenerTypeException: If the event listener type is not supported.
|
|
50
|
+
"""
|
|
34
51
|
wrapped_events: EventListenerEvents = {"on_resync": self.events["on_resync"]}
|
|
35
52
|
event_listener: BaseEventListener
|
|
36
53
|
config = self.context.config.event_listener
|
|
File without changes
|
|
@@ -13,6 +13,15 @@ from port_ocean.core.event_listener.base import (
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class HttpEventListenerSettings(EventListenerSettings):
|
|
16
|
+
"""
|
|
17
|
+
This class inherits from `EventListenerSettings`, which provides a foundation for defining event listener configurations.
|
|
18
|
+
The `HttpEventListenerSettings` specifically includes settings related to the HTTP event listener (Webhook).
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
type (Literal["WEBHOOK"]): A literal indicating the type of the event listener, which is set to "WEBHOOK" for this class.
|
|
22
|
+
app_host (AnyHttpUrl): The base URL of the application hosting the webhook.
|
|
23
|
+
The "AnyHttpUrl" type indicates that the value must be a valid HTTP/HTTPS URL.
|
|
24
|
+
"""
|
|
16
25
|
type: Literal["WEBHOOK"]
|
|
17
26
|
app_host: AnyHttpUrl
|
|
18
27
|
|
|
@@ -24,6 +33,16 @@ class HttpEventListenerSettings(EventListenerSettings):
|
|
|
24
33
|
|
|
25
34
|
|
|
26
35
|
class HttpEventListener(BaseEventListener):
|
|
36
|
+
"""
|
|
37
|
+
HTTP event listener that listens for webhook events and triggers "on_resync" event.
|
|
38
|
+
|
|
39
|
+
This class inherits from `BaseEventListener`, which provides a foundation for creating event listeners.
|
|
40
|
+
The `HttpEventListener` listens for HTTP POST requests to the `/resync` endpoint and triggers the "on_resync" event.
|
|
41
|
+
|
|
42
|
+
Parameters:
|
|
43
|
+
events (EventListenerEvents): A dictionary containing event types and their corresponding event handlers.
|
|
44
|
+
event_listener_config (HttpEventListenerSettings): Configuration settings for the HTTP event listener.
|
|
45
|
+
"""
|
|
27
46
|
def __init__(
|
|
28
47
|
self,
|
|
29
48
|
events: EventListenerEvents,
|
|
@@ -33,6 +52,10 @@ class HttpEventListener(BaseEventListener):
|
|
|
33
52
|
self.event_listener_config = event_listener_config
|
|
34
53
|
|
|
35
54
|
async def start(self) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Starts the HTTP event listener.
|
|
57
|
+
It sets up an APIRouter to handle the `/resync` endpoint and registers the "on_resync" event handler.
|
|
58
|
+
"""
|
|
36
59
|
logger.info("Setting up HTTP Event Listener")
|
|
37
60
|
target_channel_router = APIRouter()
|
|
38
61
|
|
|
File without changes
|
|
@@ -17,6 +17,23 @@ from port_ocean.core.event_listener.base import (
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class KafkaEventListenerSettings(EventListenerSettings):
|
|
20
|
+
"""
|
|
21
|
+
This class inherits from `EventListenerSettings`, which provides a foundation for defining event listener configurations.
|
|
22
|
+
The `KafkaEventListenerSettings` specifically includes settings related to the Kafka event listener.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
type (Literal["KAFKA"]): A literal indicating the type of the event listener, which is set to "KAFKA" for this class.
|
|
26
|
+
brokers (str): The comma-separated list of Kafka broker URLs to connect to.
|
|
27
|
+
security_protocol (str): The security protocol used for communication with Kafka brokers.
|
|
28
|
+
The default value is "SASL_SSL".
|
|
29
|
+
authentication_mechanism (str): The authentication mechanism used for secure access to Kafka brokers.
|
|
30
|
+
The default value is "SCRAM-SHA-512".
|
|
31
|
+
kafka_security_enabled (bool): A flag indicating whether Kafka security is enabled.
|
|
32
|
+
If True, credentials and security settings are used to connect to Kafka.
|
|
33
|
+
The default value is True.
|
|
34
|
+
consumer_poll_timeout (int): The maximum time in seconds to wait for messages during a poll.
|
|
35
|
+
The default value is 1 second.
|
|
36
|
+
"""
|
|
20
37
|
type: Literal["KAFKA"]
|
|
21
38
|
brokers: str = "b-1-public.publicclusterprod.t9rw6w.c1.kafka.eu-west-1.amazonaws.com:9196,b-2-public.publicclusterprod.t9rw6w.c1.kafka.eu-west-1.amazonaws.com:9196,b-3-public.publicclusterprod.t9rw6w.c1.kafka.eu-west-1.amazonaws.com:9196"
|
|
22
39
|
security_protocol: str = "SASL_SSL"
|
|
@@ -26,6 +43,16 @@ class KafkaEventListenerSettings(EventListenerSettings):
|
|
|
26
43
|
|
|
27
44
|
|
|
28
45
|
class KafkaEventListener(BaseEventListener):
|
|
46
|
+
"""
|
|
47
|
+
The `KafkaEventListener` specifically listens for messages from a Kafka consumer related to changes in an integration.
|
|
48
|
+
|
|
49
|
+
Parameters:
|
|
50
|
+
events (EventListenerEvents): A dictionary containing event types and their corresponding event handlers.
|
|
51
|
+
event_listener_config (KafkaEventListenerSettings): Configuration settings for the Kafka event listener.
|
|
52
|
+
org_id (str): The identifier of the organization associated with the integration.
|
|
53
|
+
integration_identifier (str): The identifier of the integration being monitored.
|
|
54
|
+
integration_type (str): The type of the integration being monitored.
|
|
55
|
+
"""
|
|
29
56
|
def __init__(
|
|
30
57
|
self,
|
|
31
58
|
events: EventListenerEvents,
|
|
@@ -41,6 +68,11 @@ class KafkaEventListener(BaseEventListener):
|
|
|
41
68
|
self.integration_type = integration_type
|
|
42
69
|
|
|
43
70
|
async def _get_kafka_config(self) -> KafkaConsumerConfig:
|
|
71
|
+
"""
|
|
72
|
+
A private method that returns the Kafka consumer configuration based on the provided settings.
|
|
73
|
+
If Kafka security is enabled, it fetches Kafka credentials using the ocean.port_client.get_kafka_creds() method.
|
|
74
|
+
Otherwise, it returns the KafkaConsumerConfig object parsed from the event_listener_config.
|
|
75
|
+
"""
|
|
44
76
|
if self.event_listener_config.kafka_security_enabled:
|
|
45
77
|
creds = await ocean.port_client.get_kafka_creds()
|
|
46
78
|
return KafkaConsumerConfig(
|
|
@@ -52,7 +84,11 @@ class KafkaEventListener(BaseEventListener):
|
|
|
52
84
|
|
|
53
85
|
return KafkaConsumerConfig.parse_obj(self.event_listener_config.dict())
|
|
54
86
|
|
|
55
|
-
def
|
|
87
|
+
def _should_be_processed(self, msg_value: dict[Any, Any], topic: str) -> bool:
|
|
88
|
+
"""
|
|
89
|
+
Determines if a given message should be processed based on the integration identifier and topic.
|
|
90
|
+
Returns True if the message should be processed, False otherwise.
|
|
91
|
+
"""
|
|
56
92
|
integration_identifier = (
|
|
57
93
|
msg_value.get("diff", {}).get("after", {}).get("identifier")
|
|
58
94
|
)
|
|
@@ -64,15 +100,22 @@ class KafkaEventListener(BaseEventListener):
|
|
|
64
100
|
return False
|
|
65
101
|
|
|
66
102
|
async def _handle_message(self, message: dict[Any, Any], topic: str) -> None:
|
|
67
|
-
|
|
103
|
+
"""
|
|
104
|
+
A private method that handles incoming Kafka messages.
|
|
105
|
+
If the message should be processed (determined by `_should_be_processed`), it triggers the corresponding event handler.
|
|
106
|
+
"""
|
|
107
|
+
if not self._should_be_processed(message, topic):
|
|
68
108
|
return
|
|
69
109
|
|
|
70
110
|
if "change.log" in topic and message is not None:
|
|
71
111
|
await self.events["on_resync"](message)
|
|
72
112
|
|
|
73
|
-
def
|
|
113
|
+
def _wrapped_start(
|
|
74
114
|
self, context: PortOceanContext, func: Callable[[], None]
|
|
75
115
|
) -> Callable[[], None]:
|
|
116
|
+
"""
|
|
117
|
+
A method that wraps the `start` method, initializing the PortOceanContext and invoking the given function.
|
|
118
|
+
"""
|
|
76
119
|
ocean_app = context.app
|
|
77
120
|
|
|
78
121
|
def wrapper() -> None:
|
|
@@ -82,6 +125,10 @@ class KafkaEventListener(BaseEventListener):
|
|
|
82
125
|
return wrapper
|
|
83
126
|
|
|
84
127
|
async def start(self) -> None:
|
|
128
|
+
"""
|
|
129
|
+
The main method that starts the Kafka consumer.
|
|
130
|
+
It creates a KafkaConsumer instance with the given configuration and starts it in a separate thread.
|
|
131
|
+
"""
|
|
85
132
|
consumer = KafkaConsumer(
|
|
86
133
|
msg_process=self._handle_message,
|
|
87
134
|
config=await self._get_kafka_config(),
|
|
@@ -90,5 +137,5 @@ class KafkaEventListener(BaseEventListener):
|
|
|
90
137
|
logger.info("Starting Kafka consumer")
|
|
91
138
|
threading.Thread(
|
|
92
139
|
name="ocean_kafka_consumer",
|
|
93
|
-
target=self.
|
|
140
|
+
target=self._wrapped_start(ocean, consumer.start),
|
|
94
141
|
).start()
|
|
@@ -12,6 +12,14 @@ from port_ocean.core.event_listener.polling.utils import repeat_every
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class PollingEventListenerSettings(EventListenerSettings):
|
|
15
|
+
"""
|
|
16
|
+
Attributes:
|
|
17
|
+
type (Literal["POLLING"]): A literal indicating the type of the event listener, which is set to "POLLING" for this class.
|
|
18
|
+
resync_on_start (bool): A flag indicating whether to trigger a resync event on the start of the polling event listener.
|
|
19
|
+
If True, the "on_resync" event will be triggered immediately when the polling listener starts.
|
|
20
|
+
interval (int): The interval in seconds at which the polling event listener checks for changes in the integration.
|
|
21
|
+
The default interval is set to 60 seconds.
|
|
22
|
+
"""
|
|
15
23
|
type: Literal["POLLING"]
|
|
16
24
|
resync_on_start: bool = True
|
|
17
25
|
interval: int = 60
|
|
@@ -21,6 +29,15 @@ class PollingEventListenerSettings(EventListenerSettings):
|
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
class PollingEventListener(BaseEventListener):
|
|
32
|
+
"""
|
|
33
|
+
Polling event listener that checks for changes in the integration every `interval` seconds.
|
|
34
|
+
|
|
35
|
+
The `PollingEventListener` periodically checks for changes in the integration and triggers the "on_resync" event if changes are detected.
|
|
36
|
+
|
|
37
|
+
Parameters:
|
|
38
|
+
events (EventListenerEvents): A dictionary containing event types and their corresponding event handlers.
|
|
39
|
+
event_listener_config (PollingEventListenerSettings): Configuration settings for the Polling event listener.
|
|
40
|
+
"""
|
|
24
41
|
def __init__(
|
|
25
42
|
self,
|
|
26
43
|
events: EventListenerEvents,
|
|
@@ -31,6 +48,11 @@ class PollingEventListener(BaseEventListener):
|
|
|
31
48
|
self._last_updated_at = None
|
|
32
49
|
|
|
33
50
|
async def start(self) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Starts the polling event listener.
|
|
53
|
+
It registers the "on_resync" event to be called every `interval` seconds specified in the `event_listener_config`.
|
|
54
|
+
The `on_resync` event is triggered if the integration has changed since the last update.
|
|
55
|
+
"""
|
|
34
56
|
logger.info(
|
|
35
57
|
f"Setting up Polling event listener with interval: {self.event_listener_config.interval}"
|
|
36
58
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from collections import defaultdict
|
|
2
3
|
from itertools import groupby
|
|
3
4
|
|
|
4
5
|
from port_ocean.clients.port.client import PortClient
|
|
@@ -33,12 +34,18 @@ async def get_related_entities(
|
|
|
33
34
|
for entity in entities_with_relations
|
|
34
35
|
]
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
blueprints_to_relations = defaultdict(list)
|
|
37
38
|
for entity, blueprint in entity_to_blueprint:
|
|
38
39
|
for relation_name, relation in entity.relations.items():
|
|
39
40
|
relation_blueprint = blueprint.relations[relation_name].target
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
blueprints_to_relations[relation_blueprint].extend(
|
|
42
|
+
relation if isinstance(relation, list) else [relation]
|
|
42
43
|
)
|
|
43
44
|
|
|
44
|
-
return
|
|
45
|
+
return [
|
|
46
|
+
Entity(identifier=relation, blueprint=blueprint)
|
|
47
|
+
for blueprint, relations in blueprints_to_relations.items()
|
|
48
|
+
# multiple entities can point to the same relation in the same blueprint, for performance reasons
|
|
49
|
+
# we want to avoid fetching the same relation multiple times
|
|
50
|
+
for relation in set(relations)
|
|
51
|
+
]
|
|
@@ -15,6 +15,29 @@ from port_ocean.exceptions.core import IntegrationAlreadyStartedException
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class BaseIntegration(SyncRawMixin, SyncMixin):
|
|
18
|
+
"""
|
|
19
|
+
This is the default integration class that Ocean initializes when no custom integration class is
|
|
20
|
+
provided.
|
|
21
|
+
|
|
22
|
+
This class provides a foundation for implementing various integration types. It inherits from
|
|
23
|
+
both SyncRawMixin and SyncMixin, which provide synchronization and event handling functionality.
|
|
24
|
+
|
|
25
|
+
Parameters:
|
|
26
|
+
context (PortOceanContext): The PortOceanContext object providing the necessary context
|
|
27
|
+
for the integration.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
started (bool): Flag indicating whether the integration has been started.
|
|
31
|
+
context (PortOceanContext): The PortOceanContext object containing integration context.
|
|
32
|
+
event_listener_factory (EventListenerFactory): Factory to create event listeners for
|
|
33
|
+
handling integration events.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
IntegrationAlreadyStartedException: Raised if the integration is attempted to be started
|
|
37
|
+
more than once.
|
|
38
|
+
NotImplementedError: Raised if the `on_resync` method is not implemented, and the event
|
|
39
|
+
strategy does not have a custom implementation for resync events.
|
|
40
|
+
"""
|
|
18
41
|
def __init__(self, context: PortOceanContext):
|
|
19
42
|
SyncRawMixin.__init__(self)
|
|
20
43
|
SyncMixin.__init__(self)
|
|
@@ -27,6 +50,9 @@ class BaseIntegration(SyncRawMixin, SyncMixin):
|
|
|
27
50
|
)
|
|
28
51
|
|
|
29
52
|
async def start(self) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Initializes handlers, establishes integration at the specified port, and starts the event listener.
|
|
55
|
+
"""
|
|
30
56
|
logger.info("Starting integration")
|
|
31
57
|
if self.started:
|
|
32
58
|
raise IntegrationAlreadyStartedException("Integration already started")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from contextlib import contextmanager
|
|
2
2
|
from typing import Awaitable, Generator, Callable
|
|
3
3
|
|
|
4
|
+
from loguru import logger
|
|
4
5
|
from port_ocean.core.ocean_types import (
|
|
5
6
|
ASYNC_GENERATOR_RESYNC_TYPE,
|
|
6
7
|
RAW_RESULT,
|
|
@@ -20,9 +21,9 @@ def resync_error_handling() -> Generator[None, None, None]:
|
|
|
20
21
|
except StopAsyncIteration:
|
|
21
22
|
raise
|
|
22
23
|
except Exception as error:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
) from error
|
|
24
|
+
err_msg = f"Failed to execute resync function, error: {error}"
|
|
25
|
+
logger.exception(err_msg)
|
|
26
|
+
raise OceanAbortException(err_msg) from error
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
async def resync_function_wrapper(
|
port_ocean/core/models.py
CHANGED
|
@@ -10,7 +10,7 @@ class Entity(BaseModel):
|
|
|
10
10
|
identifier: str
|
|
11
11
|
blueprint: str
|
|
12
12
|
title: str | None
|
|
13
|
-
team: str | list[str] = []
|
|
13
|
+
team: str | None | list[str | None] = []
|
|
14
14
|
properties: dict[str, Any] = {}
|
|
15
15
|
relations: dict[str, Any] = {}
|
|
16
16
|
|
|
@@ -18,19 +18,7 @@ class Entity(BaseModel):
|
|
|
18
18
|
def parse_obj(cls: Type["Model"], obj: dict[Any, Any]) -> "Model":
|
|
19
19
|
obj["identifier"] = str(obj.get("identifier"))
|
|
20
20
|
obj["blueprint"] = str(obj.get("blueprint"))
|
|
21
|
-
|
|
22
|
-
team = obj.get("team")
|
|
23
|
-
obj["team"] = (
|
|
24
|
-
[str(item) for item in team]
|
|
25
|
-
if isinstance(team, list)
|
|
26
|
-
else str(obj.get("team"))
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
for key, value in obj.get("relations", {}).items():
|
|
30
|
-
if isinstance(value, list):
|
|
31
|
-
obj["relations"][key] = [str(item) for item in value]
|
|
32
|
-
else:
|
|
33
|
-
obj["relations"][key] = str(value)
|
|
21
|
+
|
|
34
22
|
return super(Entity, cls).parse_obj(obj)
|
|
35
23
|
|
|
36
24
|
|
port_ocean/logger_setup.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
|
|
3
|
+
import loguru
|
|
3
4
|
from loguru import logger
|
|
4
5
|
|
|
5
6
|
from port_ocean.config.settings import LogLevelType
|
|
@@ -20,3 +21,16 @@ def setup_logger(level: LogLevelType) -> None:
|
|
|
20
21
|
enqueue=True, # process logs in background
|
|
21
22
|
diagnose=False, # hide variable values in log backtrace
|
|
22
23
|
)
|
|
24
|
+
logger.configure(patcher=exception_deserializer)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def exception_deserializer(record: "loguru.Record") -> None:
|
|
28
|
+
"""
|
|
29
|
+
Workaround for when trying to log exception objects with loguru.
|
|
30
|
+
loguru doesn't able to deserialize `Exception` subclasses.
|
|
31
|
+
https://github.com/Delgan/loguru/issues/504#issuecomment-917365972
|
|
32
|
+
"""
|
|
33
|
+
exception: loguru.RecordException | None = record["exception"]
|
|
34
|
+
if exception is not None:
|
|
35
|
+
fixed = Exception(str(exception.value))
|
|
36
|
+
record["exception"] = exception._replace(value=fixed)
|
|
@@ -4,7 +4,7 @@ port_ocean/cli/cli.py,sha256=RvWTELEn5YFw9aM0vaNqm5YqZZrL50ILaBs27ptiGl0,57
|
|
|
4
4
|
port_ocean/cli/commands/__init__.py,sha256=Wb3h7y-fjXCDZ_-WLDIh5Jl8R7QO74iZqUJYsRD9wNY,278
|
|
5
5
|
port_ocean/cli/commands/list_integrations.py,sha256=DVVioFruGUE-_v6UUHlcemWNN6RlWwCrf1X4HmAXsf8,1134
|
|
6
6
|
port_ocean/cli/commands/main.py,sha256=gj0lmuLep2XeLNuabB7Wk0UVYPT7_CD_rAw5AoUQWSE,1057
|
|
7
|
-
port_ocean/cli/commands/new.py,sha256=
|
|
7
|
+
port_ocean/cli/commands/new.py,sha256=net7346AnM4f4Mfy9dZWnPJzXicgvfCqM6lbsByicsU,1816
|
|
8
8
|
port_ocean/cli/commands/pull.py,sha256=VvrRjLNlfPuLIf7KzeIcbzzdi98Z0M9wCRpXC3QPxdI,2306
|
|
9
9
|
port_ocean/cli/commands/sail.py,sha256=6qEhg6hWoQgHrkg-eRh_uYuVWc05K7pukFfSjYFD01o,1534
|
|
10
10
|
port_ocean/cli/commands/version.py,sha256=hEuIEIcm6Zkamz41Z9nxeSM_4g3oNlAgWwQyDGboh-E,536
|
|
@@ -22,7 +22,7 @@ port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/config.yaml,sha256
|
|
|
22
22
|
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py,sha256=_TRsA2s6GV2E3CTI8CHcsH-ZuH4_Eh5-juDXWaET0ho,65
|
|
23
23
|
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py,sha256=KxxtBrRygYOP0bulXySVYwX0fm_mm3QHqeEqwDwgXgY,1669
|
|
24
24
|
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml,sha256=kENq8nNmFPIzw9vySheyG4xHxAPuBSpZO1CYGD6G2NE,46
|
|
25
|
-
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml,sha256=
|
|
25
|
+
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml,sha256=u9tb2IFQApl_GSy_2C59GRN88fc3XqEkMn-XOIhMrZ0,1941
|
|
26
26
|
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
port_ocean/cli/utils.py,sha256=IUK2UbWqjci-lrcDdynZXqVP5B5TcjF0w5CpEVUks-k,54
|
|
28
28
|
port_ocean/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -36,7 +36,7 @@ port_ocean/clients/port/mixins/integrations.py,sha256=wbK3EXqPv5pRZytQjIVKwDa2aL
|
|
|
36
36
|
port_ocean/clients/port/types.py,sha256=NnT1W2H_tZ_9cyEFoDhb90q3apf8p9emkHYspbPoZJQ,593
|
|
37
37
|
port_ocean/clients/port/utils.py,sha256=jgF0Gq8lZA9qkpsHlzPTuKoAdY6AlvF1Bl5JB4SnQGE,781
|
|
38
38
|
port_ocean/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
port_ocean/config/base.py,sha256=
|
|
39
|
+
port_ocean/config/base.py,sha256=nqHIZJZK3IyR48cOQGKQaQv4kz5IG_oUhrP9dJScC-U,4011
|
|
40
40
|
port_ocean/config/dynamic.py,sha256=CIRDnqFUPSnNMLZ-emRCMVAjEQNBlIujhZ7OGwi_aKs,1816
|
|
41
41
|
port_ocean/config/settings.py,sha256=Ghg71GJgeIPzFMyvAanErfCMCVDbc-roDGkdtgtTYf8,1231
|
|
42
42
|
port_ocean/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -48,18 +48,20 @@ port_ocean/context/ocean.py,sha256=tb6S9s5gV-Dyph0_503zXCZJr6kjiXgdrQ5MNztzeKk,4
|
|
|
48
48
|
port_ocean/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
49
|
port_ocean/core/base.py,sha256=gyF4b5_3YbO9u7wq9_HHSYbU5oouAWGuLibBfJteKE0,219
|
|
50
50
|
port_ocean/core/event_listener/__init__.py,sha256=fsSfwMB44pi6Wb8mckbPXxv4vcbobyALmUF66PS0rjw,607
|
|
51
|
-
port_ocean/core/event_listener/base.py,sha256=
|
|
52
|
-
port_ocean/core/event_listener/factory.py,sha256=
|
|
53
|
-
port_ocean/core/event_listener/http/
|
|
54
|
-
port_ocean/core/event_listener/
|
|
55
|
-
port_ocean/core/event_listener/
|
|
51
|
+
port_ocean/core/event_listener/base.py,sha256=QhZBDGw-ji6fpl4uG2UvMrwNRVuUW9KeolmfIphI4XE,891
|
|
52
|
+
port_ocean/core/event_listener/factory.py,sha256=iPAqZOzxcc-0-NKIEIxdkRwZ9a1EtQTBkGMjjOj3amE,3474
|
|
53
|
+
port_ocean/core/event_listener/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
|
+
port_ocean/core/event_listener/http/event_listener.py,sha256=Piv5_9xq6hhKVuKw4XYEQ_FzXwYVeC0IBdAXXK4Dlmc,2511
|
|
55
|
+
port_ocean/core/event_listener/kafka/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
|
+
port_ocean/core/event_listener/kafka/event_listener.py,sha256=B5TlSa3Zqlnf_Z-pBb3RR3JspkqOWiD38Uv6KY6nmPI,6351
|
|
57
|
+
port_ocean/core/event_listener/polling/event_listener.py,sha256=fkL5dAljXCfucl3A6YyfgsMPYkRodh0AUr1fMsMLU-Q,3264
|
|
56
58
|
port_ocean/core/event_listener/polling/utils.py,sha256=qjBdLP0Wnn2T3U97SXHBsOTqP0QMzVLdiIIXdFUaazs,2799
|
|
57
59
|
port_ocean/core/handlers/__init__.py,sha256=EW54s6OkpSYCG2CYstSqkgneI6KjvigEeC1Cxg5V2bM,716
|
|
58
60
|
port_ocean/core/handlers/entities_state_applier/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
61
|
port_ocean/core/handlers/entities_state_applier/base.py,sha256=P31b_5JU5AbnblNFY4HD3oeXSH1spNtCZUJjpchWcAI,869
|
|
60
62
|
port_ocean/core/handlers/entities_state_applier/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
63
|
port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=SCgcY01aCTLjCkikxhy6_4ChWNsOv_pWP8bZReOTfJI,7977
|
|
62
|
-
port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py,sha256=
|
|
64
|
+
port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py,sha256=1zncwCbE-Gej0xaWKlzZgoXxOBe9bgs_YxlZ8QW3NdI,1751
|
|
63
65
|
port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py,sha256=lbFwg5mRYxulYBvo5KmN5vQ59a-ChWzwwUtqfyigR-o,988
|
|
64
66
|
port_ocean/core/handlers/entities_state_applier/port/validate_entity_relations.py,sha256=nKuQ-RlalGG07olxm6l5NHeOuQT9dEZLoMpD-AN5nq0,1392
|
|
65
67
|
port_ocean/core/handlers/entity_processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -70,13 +72,13 @@ port_ocean/core/handlers/port_app_config/api.py,sha256=Q1dDO7YUVQ_Yb7LqBC4i1Ww-G
|
|
|
70
72
|
port_ocean/core/handlers/port_app_config/base.py,sha256=DAj8QxFyMw5o6n1HO92MqelOJJaAauSOaUp0otsG3uI,651
|
|
71
73
|
port_ocean/core/handlers/port_app_config/models.py,sha256=pkVIWQlJJgG168MUxu2Dzirt5H03B9e7WkVY0E3Q1Ig,1810
|
|
72
74
|
port_ocean/core/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
port_ocean/core/integrations/base.py,sha256=
|
|
75
|
+
port_ocean/core/integrations/base.py,sha256=fTqpl6Zz0qSeFWIz4InVhphOfeu_SFoe3JlvoiaAgnY,3162
|
|
74
76
|
port_ocean/core/integrations/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
75
77
|
port_ocean/core/integrations/mixins/events.py,sha256=yjh5jmZcGoklqDIV8MBvTCGK1r_b7oc9H7htIvssfGk,698
|
|
76
78
|
port_ocean/core/integrations/mixins/handler.py,sha256=3chtZ1RCIH2h1V19hd9CteAV8M0wuhTNnta6_Jlviog,2569
|
|
77
79
|
port_ocean/core/integrations/mixins/sync.py,sha256=OFJsK_7yV9Wvnb9STCBiX2FGAS1j-R8sIZsxM7m-i3M,11843
|
|
78
|
-
port_ocean/core/integrations/mixins/utils.py,sha256=
|
|
79
|
-
port_ocean/core/models.py,sha256=
|
|
80
|
+
port_ocean/core/integrations/mixins/utils.py,sha256=KC3wEbNmCFRBoM0BdMumOdXp-Fj5rHF39ZPjifD63ao,1747
|
|
81
|
+
port_ocean/core/models.py,sha256=WuG92EAVXixehj6hU2x24AR9kdAWPJfvhkgjCR4aGO0,909
|
|
80
82
|
port_ocean/core/ocean_types.py,sha256=gQANgXkxYXV98M9ukQfv_UCuUFRBrhyYN9I707OLKqM,781
|
|
81
83
|
port_ocean/core/utils.py,sha256=B040Wokk28g9tQj_06qk_uvm85RIXc6XGXysZV6gtQw,1957
|
|
82
84
|
port_ocean/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -86,15 +88,15 @@ port_ocean/exceptions/clients.py,sha256=LKLLs-Zy3caNG85rwxfOw2rMr8qqVV6SHUq4fRCZ
|
|
|
86
88
|
port_ocean/exceptions/context.py,sha256=8pUlWbXpRof5tlArpPooXCbEVHQCyY2sBAnMgPLzyEM,235
|
|
87
89
|
port_ocean/exceptions/core.py,sha256=jABHL3elMfGiGXXB_OlAWh34vU4qaanMNw2Uw-_SpmI,532
|
|
88
90
|
port_ocean/exceptions/port_defaults.py,sha256=R3ufJcfllb7NZSwHOpBs8kbjsIZVQLM6vKO6dz4w-EE,407
|
|
89
|
-
port_ocean/logger_setup.py,sha256=
|
|
91
|
+
port_ocean/logger_setup.py,sha256=UGTvSdbV2FMvDCQRxCGcBw6m9VpEkl-pGBNOL47BrH8,1107
|
|
90
92
|
port_ocean/middlewares.py,sha256=8rGu9XSKvbNCQGzWvfaijDrp-0ATJrWAQfBji2CnSck,2475
|
|
91
93
|
port_ocean/ocean.py,sha256=CBuRr1gJRpWRJ6TUStG-ocLptHohFALgJqFXMItWHL0,2263
|
|
92
94
|
port_ocean/port_defaults.py,sha256=QzrO_7Q0hRX-bgMpm-6XTedMbTZQH73NfHby3l1FEDw,7161
|
|
93
95
|
port_ocean/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
96
|
port_ocean/run.py,sha256=Bd6c6qYqQZlZqTLyRWRIkf579NQNqrr-o4mVAnBIISA,2834
|
|
95
97
|
port_ocean/utils.py,sha256=2a30qmGMSPgsTxfpz2RiI3xDsM0-TkaKAbvJBaYqvCc,1007
|
|
96
|
-
port_ocean-0.1.
|
|
97
|
-
port_ocean-0.1.
|
|
98
|
-
port_ocean-0.1.
|
|
99
|
-
port_ocean-0.1.
|
|
100
|
-
port_ocean-0.1.
|
|
98
|
+
port_ocean-0.1.4.dev1.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
99
|
+
port_ocean-0.1.4.dev1.dist-info/METADATA,sha256=aKR6P1fwHSM0S1KPmZzGFjHdUwh4YP8DFcQloH7gQ7s,6361
|
|
100
|
+
port_ocean-0.1.4.dev1.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
|
101
|
+
port_ocean-0.1.4.dev1.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
|
|
102
|
+
port_ocean-0.1.4.dev1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|