port-ocean 0.13.1.dev1__py3-none-any.whl → 0.14.0__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.

@@ -51,4 +51,6 @@ def clean(path: str, force: bool, wait: bool) -> None:
51
51
  default_app,
52
52
  )
53
53
 
54
- clean_defaults(app.integration.AppConfigHandlerClass.CONFIG_CLASS, force, wait)
54
+ clean_defaults(
55
+ app.integration.AppConfigHandlerClass.CONFIG_CLASS, app.config, force, wait
56
+ )
@@ -78,6 +78,7 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
78
78
  default_factory=lambda: IntegrationSettings(type="", identifier="")
79
79
  )
80
80
  runtime: Runtime = Runtime.OnPrem
81
+ resources_path: str = Field(default=".port/resources")
81
82
 
82
83
  @root_validator()
83
84
  def validate_integration_config(cls, values: dict[str, Any]) -> dict[str, Any]:
@@ -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], force: bool, wait: bool
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(config_class)
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
@@ -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], base_path: Path = Path(".")
86
+ port_app_config_class: Type[PortAppConfig],
87
+ custom_defaults_dir: Optional[str] = None,
88
+ base_path: Path = Path("."),
82
89
  ) -> Defaults | None:
83
- defaults_dir = base_path / ".port/resources"
84
- if not defaults_dir.exists():
85
- return None
86
-
87
- if not defaults_dir.is_dir():
88
- raise UnsupportedDefaultFileType(
89
- f"Defaults directory is not a directory: {defaults_dir}"
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()
@@ -198,7 +198,9 @@ async def _initialize_defaults(
198
198
  config_class: Type[PortAppConfig], integration_config: IntegrationConfiguration
199
199
  ) -> None:
200
200
  port_client = ocean.port_client
201
- defaults = get_port_integration_defaults(config_class)
201
+ defaults = get_port_integration_defaults(
202
+ config_class, integration_config.resources_path
203
+ )
202
204
  if not defaults:
203
205
  logger.warning("No defaults found. Skipping initialization...")
204
206
  return None
port_ocean/ocean.py CHANGED
@@ -114,7 +114,7 @@ class Ocean:
114
114
  )
115
115
  await repeated_function()
116
116
 
117
- def initialize_app(self):
117
+ def initialize_app(self) -> None:
118
118
  self.fast_api_app.include_router(self.integration_router, prefix="/integration")
119
119
 
120
120
  @asynccontextmanager
@@ -0,0 +1,166 @@
1
+ import pytest
2
+ import json
3
+ from unittest.mock import patch
4
+ from pathlib import Path
5
+ from port_ocean.core.handlers.port_app_config.models import PortAppConfig
6
+ from port_ocean.core.defaults.common import (
7
+ get_port_integration_defaults,
8
+ Defaults,
9
+ )
10
+
11
+
12
+ @pytest.fixture
13
+ def setup_mock_directories(tmp_path: Path) -> tuple[Path, Path, Path]:
14
+ # Create .port/resources with sample files
15
+ default_dir = tmp_path / ".port/resources"
16
+ default_dir.mkdir(parents=True, exist_ok=True)
17
+
18
+ # Create mock JSON and YAML files with expected content
19
+ (default_dir / "blueprints.json").write_text(
20
+ json.dumps(
21
+ [
22
+ {
23
+ "identifier": "mock-identifier",
24
+ "title": "mock-title",
25
+ "icon": "mock-icon",
26
+ "schema": {
27
+ "type": "object",
28
+ "properties": {"key": {"type": "string"}},
29
+ },
30
+ }
31
+ ]
32
+ )
33
+ )
34
+ (default_dir / "port-app-config.json").write_text(
35
+ json.dumps(
36
+ {
37
+ "resources": [
38
+ {
39
+ "kind": "mock-kind",
40
+ "selector": {"query": "true"},
41
+ "port": {
42
+ "entity": {
43
+ "mappings": {
44
+ "identifier": ".id",
45
+ "title": ".title",
46
+ "blueprint": '"mock-identifier"',
47
+ }
48
+ }
49
+ },
50
+ }
51
+ ]
52
+ }
53
+ )
54
+ )
55
+
56
+ # Create .port/custom_resources with different sample files
57
+ custom_resources_dir = tmp_path / ".port/custom_resources"
58
+ custom_resources_dir.mkdir(parents=True, exist_ok=True)
59
+
60
+ # Create mock JSON and YAML files with expected content
61
+ (custom_resources_dir / "blueprints.json").write_text(
62
+ json.dumps(
63
+ [
64
+ {
65
+ "identifier": "mock-custom-identifier",
66
+ "title": "mock-custom-title",
67
+ "icon": "mock-custom-icon",
68
+ "schema": {
69
+ "type": "object",
70
+ "properties": {"key": {"type": "string"}},
71
+ },
72
+ }
73
+ ]
74
+ )
75
+ )
76
+ (custom_resources_dir / "port-app-config.json").write_text(
77
+ json.dumps(
78
+ {
79
+ "resources": [
80
+ {
81
+ "kind": "mock-custom-kind",
82
+ "selector": {"query": "true"},
83
+ "port": {
84
+ "entity": {
85
+ "mappings": {
86
+ "identifier": ".id",
87
+ "title": ".title",
88
+ "blueprint": '"mock-custom-identifier"',
89
+ }
90
+ }
91
+ },
92
+ }
93
+ ]
94
+ }
95
+ )
96
+ )
97
+
98
+ # Define the non-existing directory path
99
+ non_existing_dir = tmp_path / ".port/do_not_exist"
100
+
101
+ return default_dir, custom_resources_dir, non_existing_dir
102
+
103
+
104
+ def test_custom_defaults_dir_used_if_valid(
105
+ setup_mock_directories: tuple[Path, Path, Path]
106
+ ) -> None:
107
+ # Arrange
108
+ _, custom_resources_dir, _ = setup_mock_directories
109
+
110
+ with (
111
+ patch("port_ocean.core.defaults.common.is_valid_dir") as mock_is_valid_dir,
112
+ patch(
113
+ "pathlib.Path.iterdir",
114
+ return_value=custom_resources_dir.iterdir(),
115
+ ),
116
+ ):
117
+ mock_is_valid_dir.side_effect = lambda path: path == custom_resources_dir
118
+
119
+ # Act
120
+ defaults = get_port_integration_defaults(
121
+ port_app_config_class=PortAppConfig,
122
+ custom_defaults_dir=".port/custom_resources",
123
+ base_path=custom_resources_dir.parent.parent,
124
+ )
125
+
126
+ # Assert
127
+ assert isinstance(defaults, Defaults)
128
+ assert defaults.blueprints[0].get("identifier") == "mock-custom-identifier"
129
+ assert defaults.port_app_config is not None
130
+ assert defaults.port_app_config.resources[0].kind == "mock-custom-kind"
131
+
132
+
133
+ def test_fallback_to_default_dir_if_custom_dir_invalid(
134
+ setup_mock_directories: tuple[Path, Path, Path]
135
+ ) -> None:
136
+ resources_dir, _, non_existing_dir = setup_mock_directories
137
+
138
+ # Arrange
139
+ with (
140
+ patch("port_ocean.core.defaults.common.is_valid_dir") as mock_is_valid_dir,
141
+ patch("pathlib.Path.iterdir", return_value=resources_dir.iterdir()),
142
+ ):
143
+
144
+ mock_is_valid_dir.side_effect = lambda path: path == resources_dir
145
+
146
+ # Act
147
+ custom_defaults_dir = str(non_existing_dir.relative_to(resources_dir.parent))
148
+ defaults = get_port_integration_defaults(
149
+ port_app_config_class=PortAppConfig,
150
+ custom_defaults_dir=custom_defaults_dir,
151
+ base_path=resources_dir.parent.parent,
152
+ )
153
+
154
+ # Assert
155
+ assert isinstance(defaults, Defaults)
156
+ assert defaults.blueprints[0].get("identifier") == "mock-identifier"
157
+ assert defaults.port_app_config is not None
158
+ assert defaults.port_app_config.resources[0].kind == "mock-kind"
159
+
160
+
161
+ def test_default_resources_path_does_not_exist() -> None:
162
+ # Act
163
+ defaults = get_port_integration_defaults(port_app_config_class=PortAppConfig)
164
+
165
+ # Assert
166
+ assert defaults is None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.13.1.dev1
3
+ Version: 0.14.0
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
@@ -4,7 +4,7 @@ port_ocean/cli/__init__.py,sha256=ZjTGS305llhbjC2BH2KkVj34gCASBGwqc5HZEO_0T_Q,32
4
4
  port_ocean/cli/cli.py,sha256=RvWTELEn5YFw9aM0vaNqm5YqZZrL50ILaBs27ptiGl0,57
5
5
  port_ocean/cli/commands/__init__.py,sha256=Y9Q6jeYw_ZAZ-mdfE_5DZTdS2KHhieQZoUTggk_AkwM,369
6
6
  port_ocean/cli/commands/defaults/__init___.py,sha256=5OKgakO79bTbplFv1_yWCrw1x_JJqSdRXAHHqKSuEpQ,88
7
- port_ocean/cli/commands/defaults/clean.py,sha256=b3MVxAc9pASGxG3O6AjbDJiFngw82UqclVuCZObVPGM,1544
7
+ port_ocean/cli/commands/defaults/clean.py,sha256=258rOSC1xGT-NTBIkv-V8HTY57ZipnOD-gV5yEenut0,1570
8
8
  port_ocean/cli/commands/defaults/dock.py,sha256=pFtHrU_LTvb5Ddrzj09Wxy-jg1Ym10wBYD-0tpDRugE,1104
9
9
  port_ocean/cli/commands/defaults/group.py,sha256=hii_4CYoQ7jSMePbnP4AmruO_RKWCUcoV7dXXBlZafc,115
10
10
  port_ocean/cli/commands/list_integrations.py,sha256=DVVioFruGUE-_v6UUHlcemWNN6RlWwCrf1X4HmAXsf8,1134
@@ -52,7 +52,7 @@ port_ocean/clients/port/utils.py,sha256=5B6rHgiVrtiL4YWh7Eq7_ncIeDwrDsB7jIvRik5x
52
52
  port_ocean/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  port_ocean/config/base.py,sha256=x1gFbzujrxn7EJudRT81C6eN9WsYAb3vOHwcpcpX8Tc,6370
54
54
  port_ocean/config/dynamic.py,sha256=qOFkRoJsn_BW7581omi_AoMxoHqasf_foxDQ_G11_SI,2030
55
- port_ocean/config/settings.py,sha256=77MtPz-ZpOWjuuOcorAy4MSNuYhRE2a9DDBKx3sRrV8,4274
55
+ port_ocean/config/settings.py,sha256=65LATPdQ4YBdUTgQHMtZvzzPVrOxWUqAP7eerwFGZvg,4333
56
56
  port_ocean/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  port_ocean/consumers/kafka_consumer.py,sha256=N8KocjBi9aR0BOPG8hgKovg-ns_ggpEjrSxqSqF_BSo,4710
58
58
  port_ocean/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -61,9 +61,9 @@ port_ocean/context/ocean.py,sha256=2EreWOj-N2H7QUjEt5wGiv5KHP4pTZc70tn_wHcpF4w,4
61
61
  port_ocean/context/resource.py,sha256=yDj63URzQelj8zJPh4BAzTtPhpKr9Gw9DRn7I_0mJ1s,1692
62
62
  port_ocean/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
63
  port_ocean/core/defaults/__init__.py,sha256=8qCZg8n06WAdMu9s_FiRtDYLGPGHbOuS60vapeUoAks,142
64
- port_ocean/core/defaults/clean.py,sha256=S3UAfca-oU89WJKIB4OgGjGjPr0vxBQ2aRZsLTZhQ04,2185
65
- port_ocean/core/defaults/common.py,sha256=uVUg6VEn4RqtXQwLwMNGfkmT5zYRN_h5USfKw3poVyo,3561
66
- port_ocean/core/defaults/initialize.py,sha256=qg4JLIWjp0c5-9w09X99muHRYX4k1cGZ_77vYo-tnpU,8422
64
+ port_ocean/core/defaults/clean.py,sha256=TOVe5b5FAjFspAkQuKA70k2BClCEFbrQ3xgiAoKXKYE,2427
65
+ port_ocean/core/defaults/common.py,sha256=zJsj7jvlqIMLGXhdASUlbKS8GIAf-FDKKB0O7jB6nx0,4166
66
+ port_ocean/core/defaults/initialize.py,sha256=M1EXgfbnrvP5e3f9or8Bi0-4zXECznfRfy7sJn2Gc14,8471
67
67
  port_ocean/core/event_listener/__init__.py,sha256=mzJ33wRq0kh60fpVdOHVmvMTUQIvz3vxmifyBgwDn0E,889
68
68
  port_ocean/core/event_listener/base.py,sha256=1Nmpg00OfT2AD2L8eFm4VQEcdG2TClpSWJMhWhAjkEE,2356
69
69
  port_ocean/core/event_listener/factory.py,sha256=AYYfSHPAF7P5H-uQECXT0JVJjKDHrYkWJJBSL4mGkg8,3697
@@ -115,13 +115,14 @@ port_ocean/log/handlers.py,sha256=ncVjgqrZRh6BhyRrA6DQG86Wsbxph1yWYuEC0cWfe-Q,36
115
115
  port_ocean/log/logger_setup.py,sha256=CoEDowe5OwNOL_5clU6Z4faktfh0VWaOTS0VLmyhHjw,2404
116
116
  port_ocean/log/sensetive.py,sha256=lVKiZH6b7TkrZAMmhEJRhcl67HNM94e56x12DwFgCQk,2920
117
117
  port_ocean/middlewares.py,sha256=9wYCdyzRZGK1vjEJ28FY_DkfwDNENmXp504UKPf5NaQ,2727
118
- port_ocean/ocean.py,sha256=oUNY9XT30oZ6e6asD6yM2dbd01-uD6RvtJXkmobUn7U,5282
118
+ port_ocean/ocean.py,sha256=NVdvooP0AiGyVs38VE8xXj6bzeT3XLoIPnh5FAS5ouM,5290
119
119
  port_ocean/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
120
  port_ocean/run.py,sha256=rTxBlrQd4yyrtgErCFJCHCEHs7d1OXrRiJehUYmIbN0,2212
121
121
  port_ocean/sonar-project.properties,sha256=X_wLzDOkEVmpGLRMb2fg9Rb0DxWwUFSvESId8qpvrPI,73
122
122
  port_ocean/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
123
123
  port_ocean/tests/clients/port/mixins/test_entities.py,sha256=A9myrnkLhKSQrnOLv1Zz2wiOVSxW65Q9RIUIRbn_V7w,1586
124
124
  port_ocean/tests/conftest.py,sha256=JXASSS0IY0nnR6bxBflhzxS25kf4iNaABmThyZ0mZt8,101
125
+ port_ocean/tests/core/defaults/test_common.py,sha256=sR7RqB3ZYV6Xn6NIg-c8k5K6JcGsYZ2SCe_PYX5vLYM,5560
125
126
  port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py,sha256=Yv03P-LDcJCKZ21exiTFrcT1eu0zn6Z954dilxrb52Y,10842
126
127
  port_ocean/tests/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
128
  port_ocean/tests/helpers/fixtures.py,sha256=IQEplbHhRgjrAsZlnXrgSYA5YQEn25I9HgO3_Fjibxg,1481
@@ -142,8 +143,8 @@ port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,32
142
143
  port_ocean/utils/signal.py,sha256=K-6kKFQTltcmKDhtyZAcn0IMa3sUpOHGOAUdWKgx0_E,1369
143
144
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
144
145
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
145
- port_ocean-0.13.1.dev1.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
146
- port_ocean-0.13.1.dev1.dist-info/METADATA,sha256=y1F3De2qTYoHTZufqBoGQA2n-Y-dtmk8dJj4SMNtS0g,6678
147
- port_ocean-0.13.1.dev1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
148
- port_ocean-0.13.1.dev1.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
149
- port_ocean-0.13.1.dev1.dist-info/RECORD,,
146
+ port_ocean-0.14.0.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
147
+ port_ocean-0.14.0.dist-info/METADATA,sha256=A_jE7vo5o-cE6dE8fLzRZGFh7wOJFNquJN5LyZ_ygCA,6673
148
+ port_ocean-0.14.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
149
+ port_ocean-0.14.0.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
150
+ port_ocean-0.14.0.dist-info/RECORD,,