port-ocean 0.5.5__py3-none-any.whl → 0.17.8__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.
- integrations/_infra/Dockerfile.Deb +56 -0
- integrations/_infra/Dockerfile.alpine +108 -0
- integrations/_infra/Dockerfile.base.builder +26 -0
- integrations/_infra/Dockerfile.base.runner +13 -0
- integrations/_infra/Dockerfile.dockerignore +94 -0
- {port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}} → integrations/_infra}/Makefile +21 -8
- integrations/_infra/grpcio.sh +18 -0
- integrations/_infra/init.sh +5 -0
- port_ocean/bootstrap.py +1 -1
- port_ocean/cli/commands/defaults/clean.py +3 -1
- port_ocean/cli/commands/new.py +42 -7
- port_ocean/cli/commands/sail.py +7 -1
- port_ocean/cli/cookiecutter/cookiecutter.json +3 -0
- port_ocean/cli/cookiecutter/hooks/post_gen_project.py +20 -3
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +6 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +41 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +16 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +6 -7
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +1 -1
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +7 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +1 -0
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +16 -1
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +21 -10
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +2 -0
- port_ocean/clients/port/authentication.py +16 -4
- port_ocean/clients/port/client.py +17 -0
- port_ocean/clients/port/mixins/blueprints.py +7 -8
- port_ocean/clients/port/mixins/entities.py +108 -53
- port_ocean/clients/port/mixins/integrations.py +23 -34
- port_ocean/clients/port/retry_transport.py +0 -5
- port_ocean/clients/port/utils.py +9 -3
- port_ocean/config/base.py +16 -16
- port_ocean/config/dynamic.py +2 -0
- port_ocean/config/settings.py +79 -11
- port_ocean/context/event.py +18 -5
- port_ocean/context/ocean.py +14 -3
- port_ocean/core/defaults/clean.py +10 -3
- port_ocean/core/defaults/common.py +25 -9
- port_ocean/core/defaults/initialize.py +111 -100
- port_ocean/core/event_listener/__init__.py +8 -0
- port_ocean/core/event_listener/base.py +49 -10
- port_ocean/core/event_listener/factory.py +9 -1
- port_ocean/core/event_listener/http.py +11 -3
- port_ocean/core/event_listener/kafka.py +24 -5
- port_ocean/core/event_listener/once.py +96 -4
- port_ocean/core/event_listener/polling.py +16 -14
- port_ocean/core/event_listener/webhooks_only.py +41 -0
- port_ocean/core/handlers/__init__.py +1 -2
- port_ocean/core/handlers/entities_state_applier/base.py +4 -1
- port_ocean/core/handlers/entities_state_applier/port/applier.py +29 -87
- port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +5 -2
- port_ocean/core/handlers/entity_processor/base.py +26 -22
- port_ocean/core/handlers/entity_processor/jq_entity_processor.py +253 -45
- port_ocean/core/handlers/port_app_config/base.py +55 -15
- port_ocean/core/handlers/port_app_config/models.py +24 -5
- port_ocean/core/handlers/resync_state_updater/__init__.py +5 -0
- port_ocean/core/handlers/resync_state_updater/updater.py +84 -0
- port_ocean/core/integrations/base.py +5 -7
- port_ocean/core/integrations/mixins/events.py +3 -1
- port_ocean/core/integrations/mixins/sync.py +4 -2
- port_ocean/core/integrations/mixins/sync_raw.py +209 -74
- port_ocean/core/integrations/mixins/utils.py +1 -1
- port_ocean/core/models.py +44 -0
- port_ocean/core/ocean_types.py +29 -11
- port_ocean/core/utils/entity_topological_sorter.py +90 -0
- port_ocean/core/utils/utils.py +109 -0
- port_ocean/debug_cli.py +5 -0
- port_ocean/exceptions/core.py +4 -0
- port_ocean/exceptions/port_defaults.py +0 -2
- port_ocean/helpers/retry.py +85 -24
- port_ocean/log/handlers.py +23 -2
- port_ocean/log/logger_setup.py +8 -1
- port_ocean/log/sensetive.py +25 -10
- port_ocean/middlewares.py +10 -2
- port_ocean/ocean.py +57 -24
- port_ocean/run.py +10 -5
- port_ocean/tests/__init__.py +0 -0
- port_ocean/tests/clients/port/mixins/test_entities.py +53 -0
- port_ocean/tests/conftest.py +4 -0
- port_ocean/tests/core/defaults/test_common.py +166 -0
- port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +350 -0
- port_ocean/tests/core/handlers/mixins/test_sync_raw.py +552 -0
- port_ocean/tests/core/test_utils.py +73 -0
- port_ocean/tests/core/utils/test_entity_topological_sorter.py +99 -0
- port_ocean/tests/helpers/__init__.py +0 -0
- port_ocean/tests/helpers/fake_port_api.py +191 -0
- port_ocean/tests/helpers/fixtures.py +46 -0
- port_ocean/tests/helpers/integration.py +31 -0
- port_ocean/tests/helpers/ocean_app.py +66 -0
- port_ocean/tests/helpers/port_client.py +21 -0
- port_ocean/tests/helpers/smoke_test.py +82 -0
- port_ocean/tests/log/test_handlers.py +71 -0
- port_ocean/tests/test_smoke.py +74 -0
- port_ocean/tests/utils/test_async_iterators.py +45 -0
- port_ocean/tests/utils/test_cache.py +189 -0
- port_ocean/utils/async_iterators.py +109 -0
- port_ocean/utils/cache.py +37 -1
- port_ocean/utils/misc.py +22 -4
- port_ocean/utils/queue_utils.py +88 -0
- port_ocean/utils/signal.py +1 -4
- port_ocean/utils/time.py +54 -0
- {port_ocean-0.5.5.dist-info → port_ocean-0.17.8.dist-info}/METADATA +27 -19
- port_ocean-0.17.8.dist-info/RECORD +164 -0
- {port_ocean-0.5.5.dist-info → port_ocean-0.17.8.dist-info}/WHEEL +1 -1
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -94
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -15
- port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/config.yaml +0 -17
- port_ocean/core/handlers/entities_state_applier/port/validate_entity_relations.py +0 -40
- port_ocean/core/utils.py +0 -65
- port_ocean-0.5.5.dist-info/RECORD +0 -129
- {port_ocean-0.5.5.dist-info → port_ocean-0.17.8.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.5.5.dist-info → port_ocean-0.17.8.dist-info}/entry_points.txt +0 -0
port_ocean/config/base.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
-
from pathlib import Path
|
|
4
3
|
from types import GenericAlias
|
|
5
4
|
from typing import Any
|
|
6
5
|
|
|
7
6
|
import yaml
|
|
8
7
|
from humps import decamelize
|
|
8
|
+
from pathlib import Path
|
|
9
9
|
from pydantic import BaseSettings
|
|
10
10
|
from pydantic.env_settings import EnvSettingsSource, InitSettingsSource
|
|
11
11
|
from pydantic.main import ModelMetaclass, BaseModel
|
|
@@ -14,18 +14,21 @@ PROVIDER_WRAPPER_PATTERN = r"{{ from (.*) }}"
|
|
|
14
14
|
PROVIDER_CONFIG_PATTERN = r"^[a-zA-Z0-9]+ .*$"
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def read_yaml_config_settings_source(
|
|
18
|
-
settings
|
|
19
|
-
) -> str:
|
|
20
|
-
yaml_file = getattr(settings.__config__, "yaml_file", "")
|
|
17
|
+
def read_yaml_config_settings_source(settings: "BaseOceanSettings") -> dict[str, Any]:
|
|
18
|
+
yaml_file = getattr(settings.Config, "yaml_file", "")
|
|
21
19
|
|
|
22
20
|
assert yaml_file, "Settings.yaml_file not properly configured"
|
|
23
|
-
path = Path(
|
|
21
|
+
path = Path(
|
|
22
|
+
getattr(
|
|
23
|
+
settings,
|
|
24
|
+
"_base_path",
|
|
25
|
+
),
|
|
26
|
+
yaml_file,
|
|
27
|
+
)
|
|
24
28
|
|
|
25
29
|
if not path.exists():
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return path.read_text("utf-8")
|
|
30
|
+
return {}
|
|
31
|
+
return yaml.safe_load(path.read_text("utf-8"))
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
def parse_config_provider(value: str) -> tuple[str, str]:
|
|
@@ -120,16 +123,15 @@ def decamelize_config(
|
|
|
120
123
|
|
|
121
124
|
|
|
122
125
|
def load_providers(
|
|
123
|
-
settings: "BaseOceanSettings", existing_values: dict[str, Any]
|
|
126
|
+
settings: "BaseOceanSettings", existing_values: dict[str, Any]
|
|
124
127
|
) -> dict[str, Any]:
|
|
125
|
-
|
|
126
|
-
data = yaml.safe_load(yaml_content)
|
|
128
|
+
data = read_yaml_config_settings_source(settings)
|
|
127
129
|
snake_case_config = decamelize_config(settings, data)
|
|
128
130
|
return parse_providers(settings, snake_case_config, existing_values)
|
|
129
131
|
|
|
130
132
|
|
|
131
133
|
class BaseOceanSettings(BaseSettings):
|
|
132
|
-
|
|
134
|
+
_base_path: str = "./"
|
|
133
135
|
|
|
134
136
|
def get_sensitive_fields_data(self) -> set[str]:
|
|
135
137
|
return _get_sensitive_information(self)
|
|
@@ -152,9 +154,7 @@ class BaseOceanSettings(BaseSettings):
|
|
|
152
154
|
return (
|
|
153
155
|
init_settings,
|
|
154
156
|
env_settings,
|
|
155
|
-
lambda s: load_providers(
|
|
156
|
-
s, env_settings(s), init_settings.init_kwargs["base_path"]
|
|
157
|
-
),
|
|
157
|
+
lambda s: load_providers(s, {**env_settings(s), **init_settings(s)}),
|
|
158
158
|
)
|
|
159
159
|
|
|
160
160
|
|
port_ocean/config/dynamic.py
CHANGED
port_ocean/config/settings.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
from typing import Any, Literal
|
|
1
|
+
from typing import Any, Literal, Type, cast
|
|
2
2
|
|
|
3
|
-
from pydantic import Extra, AnyHttpUrl, parse_obj_as,
|
|
3
|
+
from pydantic import Extra, AnyHttpUrl, parse_obj_as, parse_raw_as
|
|
4
|
+
from pydantic.class_validators import root_validator, validator
|
|
5
|
+
from pydantic.env_settings import InitSettingsSource, EnvSettingsSource, BaseSettings
|
|
4
6
|
from pydantic.fields import Field
|
|
5
7
|
from pydantic.main import BaseModel
|
|
6
8
|
|
|
7
9
|
from port_ocean.config.base import BaseOceanSettings, BaseOceanModel
|
|
8
10
|
from port_ocean.core.event_listener import EventListenerSettingsType
|
|
11
|
+
from port_ocean.core.models import Runtime
|
|
12
|
+
from port_ocean.utils.misc import get_integration_name, get_spec_file
|
|
9
13
|
|
|
10
14
|
LogLevelType = Literal["ERROR", "WARNING", "INFO", "DEBUG", "CRITICAL"]
|
|
11
15
|
|
|
12
16
|
|
|
13
|
-
class ApplicationSettings(
|
|
17
|
+
class ApplicationSettings(BaseSettings):
|
|
14
18
|
log_level: LogLevelType = "INFO"
|
|
15
19
|
enable_http_logging: bool = True
|
|
16
20
|
port: int = 8000
|
|
@@ -21,7 +25,13 @@ class ApplicationSettings(BaseOceanModel):
|
|
|
21
25
|
env_file_encoding = "utf-8"
|
|
22
26
|
|
|
23
27
|
@classmethod
|
|
24
|
-
def customise_sources(
|
|
28
|
+
def customise_sources( # type: ignore
|
|
29
|
+
cls,
|
|
30
|
+
init_settings: InitSettingsSource,
|
|
31
|
+
env_settings: EnvSettingsSource,
|
|
32
|
+
*_,
|
|
33
|
+
**__,
|
|
34
|
+
):
|
|
25
35
|
return env_settings, init_settings
|
|
26
36
|
|
|
27
37
|
|
|
@@ -29,22 +39,80 @@ class PortSettings(BaseOceanModel, extra=Extra.allow):
|
|
|
29
39
|
client_id: str = Field(..., sensitive=True)
|
|
30
40
|
client_secret: str = Field(..., sensitive=True)
|
|
31
41
|
base_url: AnyHttpUrl = parse_obj_as(AnyHttpUrl, "https://api.getport.io")
|
|
42
|
+
port_app_config_cache_ttl: int = 60
|
|
32
43
|
|
|
33
44
|
|
|
34
45
|
class IntegrationSettings(BaseOceanModel, extra=Extra.allow):
|
|
35
46
|
identifier: str
|
|
36
47
|
type: str
|
|
37
|
-
config:
|
|
48
|
+
config: Any = Field(default_factory=dict)
|
|
38
49
|
|
|
39
|
-
@
|
|
40
|
-
def
|
|
41
|
-
|
|
50
|
+
@root_validator(pre=True)
|
|
51
|
+
def root_validator(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
52
|
+
integ_type = values.get("type")
|
|
53
|
+
|
|
54
|
+
if not integ_type:
|
|
55
|
+
integ_type = get_integration_name()
|
|
56
|
+
|
|
57
|
+
values["type"] = integ_type.lower() if integ_type else None
|
|
58
|
+
if not values.get("identifier"):
|
|
59
|
+
values["identifier"] = f"my-{integ_type}-integration".lower()
|
|
60
|
+
|
|
61
|
+
return values
|
|
42
62
|
|
|
43
63
|
|
|
44
64
|
class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
|
|
65
|
+
_integration_config_model: BaseModel | None = None
|
|
66
|
+
|
|
67
|
+
allow_environment_variables_jq_access: bool = True
|
|
45
68
|
initialize_port_resources: bool = True
|
|
46
69
|
scheduled_resync_interval: int | None = None
|
|
47
|
-
client_timeout: int =
|
|
70
|
+
client_timeout: int = 60
|
|
71
|
+
send_raw_data_examples: bool = True
|
|
48
72
|
port: PortSettings
|
|
49
|
-
event_listener: EventListenerSettingsType
|
|
50
|
-
|
|
73
|
+
event_listener: EventListenerSettingsType = Field(
|
|
74
|
+
default=cast(EventListenerSettingsType, {"type": "POLLING"})
|
|
75
|
+
)
|
|
76
|
+
# If an identifier or type is not provided, it will be generated based on the integration name
|
|
77
|
+
integration: IntegrationSettings = Field(
|
|
78
|
+
default_factory=lambda: IntegrationSettings(type="", identifier="")
|
|
79
|
+
)
|
|
80
|
+
runtime: Runtime = Runtime.OnPrem
|
|
81
|
+
resources_path: str = Field(default=".port/resources")
|
|
82
|
+
|
|
83
|
+
@root_validator()
|
|
84
|
+
def validate_integration_config(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
85
|
+
if not (config_model := values.get("_integration_config_model")):
|
|
86
|
+
return values
|
|
87
|
+
|
|
88
|
+
# Using the integration dynamic config model to parse the config
|
|
89
|
+
def parse_config(model: Type[BaseModel], config: Any) -> BaseModel:
|
|
90
|
+
# In some cases, the config is parsed as a string so we need to handle it
|
|
91
|
+
# Example: when the config is loaded from the environment variables and there is an object inside the config
|
|
92
|
+
if isinstance(config, str):
|
|
93
|
+
return parse_raw_as(model, config)
|
|
94
|
+
else:
|
|
95
|
+
return parse_obj_as(model, config)
|
|
96
|
+
|
|
97
|
+
integration_config = values["integration"]
|
|
98
|
+
integration_config.config = parse_config(
|
|
99
|
+
config_model, integration_config.config
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return values
|
|
103
|
+
|
|
104
|
+
@validator("runtime")
|
|
105
|
+
def validate_runtime(cls, runtime: Runtime) -> Runtime:
|
|
106
|
+
if runtime.is_saas_runtime:
|
|
107
|
+
spec = get_spec_file()
|
|
108
|
+
if spec is None:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
"Could not determine whether it's safe to run "
|
|
111
|
+
"the integration due to not found spec.yaml."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
saas_config = spec.get("saas")
|
|
115
|
+
if saas_config and not saas_config["enabled"]:
|
|
116
|
+
raise ValueError("This integration can't be ran as Saas")
|
|
117
|
+
|
|
118
|
+
return runtime
|
port_ocean/context/event.py
CHANGED
|
@@ -14,6 +14,7 @@ from typing import (
|
|
|
14
14
|
from uuid import uuid4
|
|
15
15
|
|
|
16
16
|
from loguru import logger
|
|
17
|
+
from port_ocean.core.utils.entity_topological_sorter import EntityTopologicalSorter
|
|
17
18
|
from pydispatch import dispatcher # type: ignore
|
|
18
19
|
from werkzeug.local import LocalStack, LocalProxy
|
|
19
20
|
|
|
@@ -24,6 +25,7 @@ from port_ocean.exceptions.context import (
|
|
|
24
25
|
)
|
|
25
26
|
from port_ocean.utils.misc import get_time
|
|
26
27
|
|
|
28
|
+
|
|
27
29
|
if TYPE_CHECKING:
|
|
28
30
|
from port_ocean.core.handlers.port_app_config.models import (
|
|
29
31
|
ResourceConfig,
|
|
@@ -50,6 +52,9 @@ class EventContext:
|
|
|
50
52
|
_parent_event: Optional["EventContext"] = None
|
|
51
53
|
_event_id: str = field(default_factory=lambda: str(uuid4()))
|
|
52
54
|
_on_abort_callbacks: list[AbortCallbackFunction] = field(default_factory=list)
|
|
55
|
+
entity_topological_sorter: EntityTopologicalSorter = field(
|
|
56
|
+
default_factory=EntityTopologicalSorter
|
|
57
|
+
)
|
|
53
58
|
|
|
54
59
|
def on_abort(self, func: AbortCallbackFunction) -> None:
|
|
55
60
|
self._on_abort_callbacks.append(func)
|
|
@@ -125,10 +130,17 @@ async def event_context(
|
|
|
125
130
|
event_type: str,
|
|
126
131
|
trigger_type: TriggerType = "manual",
|
|
127
132
|
attributes: dict[str, Any] | None = None,
|
|
133
|
+
parent_override: EventContext | None = None,
|
|
128
134
|
) -> AsyncIterator[EventContext]:
|
|
129
|
-
|
|
135
|
+
parent = parent_override or _event_context_stack.top
|
|
136
|
+
parent_attributes = parent.attributes if parent else {}
|
|
137
|
+
entity_topological_sorter = (
|
|
138
|
+
parent.entity_topological_sorter
|
|
139
|
+
if parent and parent.entity_topological_sorter
|
|
140
|
+
else EntityTopologicalSorter()
|
|
141
|
+
)
|
|
130
142
|
|
|
131
|
-
|
|
143
|
+
attributes = {**parent_attributes, **(attributes or {})}
|
|
132
144
|
new_event = EventContext(
|
|
133
145
|
event_type,
|
|
134
146
|
trigger_type=trigger_type,
|
|
@@ -136,6 +148,7 @@ async def event_context(
|
|
|
136
148
|
_parent_event=parent,
|
|
137
149
|
# inherit port app config from parent event, so it can be used in nested events
|
|
138
150
|
_port_app_config=parent.port_app_config if parent else None,
|
|
151
|
+
entity_topological_sorter=entity_topological_sorter,
|
|
139
152
|
)
|
|
140
153
|
_event_context_stack.push(new_event)
|
|
141
154
|
|
|
@@ -156,9 +169,9 @@ async def event_context(
|
|
|
156
169
|
event_kind=event.event_type,
|
|
157
170
|
event_id=event.id,
|
|
158
171
|
event_parent_id=event.parent_id,
|
|
159
|
-
event_resource_kind=
|
|
160
|
-
|
|
161
|
-
|
|
172
|
+
event_resource_kind=(
|
|
173
|
+
event.resource_config.kind if event.resource_config else None
|
|
174
|
+
),
|
|
162
175
|
):
|
|
163
176
|
logger.info("Event started")
|
|
164
177
|
try:
|
port_ocean/context/ocean.py
CHANGED
|
@@ -23,6 +23,8 @@ if TYPE_CHECKING:
|
|
|
23
23
|
from port_ocean.ocean import Ocean
|
|
24
24
|
from port_ocean.clients.port.client import PortClient
|
|
25
25
|
|
|
26
|
+
from loguru import logger
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
class PortOceanContext:
|
|
28
30
|
def __init__(self, app: Union["Ocean", None]) -> None:
|
|
@@ -63,14 +65,23 @@ class PortOceanContext:
|
|
|
63
65
|
return self.app.port_client
|
|
64
66
|
|
|
65
67
|
@property
|
|
66
|
-
def event_listener_type(
|
|
68
|
+
def event_listener_type(
|
|
69
|
+
self,
|
|
70
|
+
) -> Literal["WEBHOOK", "KAFKA", "POLLING", "ONCE", "WEBHOOKS_ONLY"]:
|
|
67
71
|
return self.app.config.event_listener.type
|
|
68
72
|
|
|
69
73
|
def on_resync(
|
|
70
74
|
self,
|
|
71
75
|
kind: str | None = None,
|
|
72
|
-
) -> Callable[[RESYNC_EVENT_LISTENER], RESYNC_EVENT_LISTENER]:
|
|
73
|
-
def wrapper(
|
|
76
|
+
) -> Callable[[RESYNC_EVENT_LISTENER | None], RESYNC_EVENT_LISTENER | None]:
|
|
77
|
+
def wrapper(
|
|
78
|
+
function: RESYNC_EVENT_LISTENER | None,
|
|
79
|
+
) -> RESYNC_EVENT_LISTENER | None:
|
|
80
|
+
if not self.app.config.event_listener.should_resync:
|
|
81
|
+
logger.debug(
|
|
82
|
+
"Webhook only event listener is used, resync events are ignored"
|
|
83
|
+
)
|
|
84
|
+
return None
|
|
74
85
|
return self.integration.on_resync(function, kind)
|
|
75
86
|
|
|
76
87
|
return wrapper
|
|
@@ -4,6 +4,7 @@ from typing import Type
|
|
|
4
4
|
import httpx
|
|
5
5
|
from loguru import logger
|
|
6
6
|
|
|
7
|
+
from port_ocean.config.settings import IntegrationConfiguration
|
|
7
8
|
from port_ocean.context.ocean import ocean
|
|
8
9
|
from port_ocean.core.defaults.common import (
|
|
9
10
|
get_port_integration_defaults,
|
|
@@ -14,12 +15,13 @@ from port_ocean.core.handlers.port_app_config.models import PortAppConfig
|
|
|
14
15
|
|
|
15
16
|
def clean_defaults(
|
|
16
17
|
config_class: Type[PortAppConfig],
|
|
18
|
+
integration_config: IntegrationConfiguration,
|
|
17
19
|
force: bool,
|
|
18
20
|
wait: bool,
|
|
19
21
|
) -> None:
|
|
20
22
|
try:
|
|
21
23
|
asyncio.new_event_loop().run_until_complete(
|
|
22
|
-
_clean_defaults(config_class, force, wait)
|
|
24
|
+
_clean_defaults(config_class, integration_config, force, wait)
|
|
23
25
|
)
|
|
24
26
|
|
|
25
27
|
except Exception as e:
|
|
@@ -27,13 +29,18 @@ def clean_defaults(
|
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
async def _clean_defaults(
|
|
30
|
-
config_class: Type[PortAppConfig],
|
|
32
|
+
config_class: Type[PortAppConfig],
|
|
33
|
+
integration_config: IntegrationConfiguration,
|
|
34
|
+
force: bool,
|
|
35
|
+
wait: bool,
|
|
31
36
|
) -> None:
|
|
32
37
|
port_client = ocean.port_client
|
|
33
38
|
is_exists = await is_integration_exists(port_client)
|
|
34
39
|
if not is_exists:
|
|
35
40
|
return None
|
|
36
|
-
defaults = get_port_integration_defaults(
|
|
41
|
+
defaults = get_port_integration_defaults(
|
|
42
|
+
config_class, integration_config.resources_path
|
|
43
|
+
)
|
|
37
44
|
if not defaults:
|
|
38
45
|
return None
|
|
39
46
|
|
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
from typing import Type, Any, TypedDict, Optional
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
|
+
from loguru import logger
|
|
6
7
|
import yaml
|
|
7
8
|
from pydantic import BaseModel, Field
|
|
8
9
|
from starlette import status
|
|
@@ -24,7 +25,7 @@ class Preset(TypedDict):
|
|
|
24
25
|
|
|
25
26
|
class Defaults(BaseModel):
|
|
26
27
|
blueprints: list[dict[str, Any]] = []
|
|
27
|
-
actions: list[
|
|
28
|
+
actions: list[dict[str, Any]] = []
|
|
28
29
|
scorecards: list[Preset] = []
|
|
29
30
|
pages: list[dict[str, Any]] = []
|
|
30
31
|
port_app_config: Optional[PortAppConfig] = Field(
|
|
@@ -77,18 +78,33 @@ def deconstruct_blueprints_to_creation_steps(
|
|
|
77
78
|
)
|
|
78
79
|
|
|
79
80
|
|
|
81
|
+
def is_valid_dir(path: Path) -> bool:
|
|
82
|
+
return path.is_dir()
|
|
83
|
+
|
|
84
|
+
|
|
80
85
|
def get_port_integration_defaults(
|
|
81
|
-
port_app_config_class: Type[PortAppConfig],
|
|
86
|
+
port_app_config_class: Type[PortAppConfig],
|
|
87
|
+
custom_defaults_dir: Optional[str] = None,
|
|
88
|
+
base_path: Path = Path("."),
|
|
82
89
|
) -> Defaults | None:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
f"
|
|
90
|
+
fallback_dir = base_path / ".port/resources"
|
|
91
|
+
|
|
92
|
+
if custom_defaults_dir and is_valid_dir(base_path / custom_defaults_dir):
|
|
93
|
+
defaults_dir = base_path / custom_defaults_dir
|
|
94
|
+
elif is_valid_dir(fallback_dir):
|
|
95
|
+
logger.info(
|
|
96
|
+
f"Could not find custom defaults directory {custom_defaults_dir}, falling back to {fallback_dir}",
|
|
97
|
+
fallback_dir=fallback_dir,
|
|
98
|
+
custom_defaults_dir=custom_defaults_dir,
|
|
99
|
+
)
|
|
100
|
+
defaults_dir = fallback_dir
|
|
101
|
+
else:
|
|
102
|
+
logger.warning(
|
|
103
|
+
f"Could not find defaults directory {fallback_dir}, skipping defaults"
|
|
90
104
|
)
|
|
105
|
+
return None
|
|
91
106
|
|
|
107
|
+
logger.info(f"Loading defaults from {defaults_dir}", defaults_dir=defaults_dir)
|
|
92
108
|
default_jsons = {}
|
|
93
109
|
allowed_file_names = [
|
|
94
110
|
field_model.alias for _, field_model in Defaults.__fields__.items()
|