port-ocean 0.10.6__py3-none-any.whl → 0.10.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.

@@ -29,7 +29,7 @@ class EntityClientMixin:
29
29
  request_options: RequestOptions,
30
30
  user_agent_type: UserAgentType | None = None,
31
31
  should_raise: bool = True,
32
- ) -> Entity:
32
+ ) -> Entity | None:
33
33
  validation_only = request_options["validation_only"]
34
34
  async with self.semaphore:
35
35
  logger.debug(
@@ -58,9 +58,16 @@ class EntityClientMixin:
58
58
  )
59
59
  handle_status_code(response, should_raise)
60
60
  result = response.json()
61
+
61
62
  result_entity = (
62
63
  Entity.parse_obj(result["entity"]) if result.get("entity") else entity
63
64
  )
65
+
66
+ # Happens when upsert fails and search identifier is defined.
67
+ # We return None to ignore the entity later in the delete process
68
+ if result_entity.is_using_search_identifier:
69
+ return None
70
+
64
71
  # In order to save memory we'll keep only the identifier, blueprint and relations of the
65
72
  # upserted entity result for later calculations
66
73
  reduced_entity = Entity(
@@ -94,7 +101,7 @@ class EntityClientMixin:
94
101
  )
95
102
  for entity in entities
96
103
  ),
97
- return_exceptions=should_raise,
104
+ return_exceptions=True,
98
105
  )
99
106
  entity_results = [
100
107
  entity for entity in modified_entities_results if isinstance(entity, Entity)
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  # The max_keepalive_connections can't be too high, as it will cause the application to run out of available connections.
19
19
  PORT_HTTP_MAX_CONNECTIONS_LIMIT = 200
20
20
  PORT_HTTP_MAX_KEEP_ALIVE_CONNECTIONS = 50
21
- PORT_HTTP_TIMEOUT = 10.0
21
+ PORT_HTTP_TIMEOUT = 60.0
22
22
 
23
23
  PORT_HTTPX_TIMEOUT = httpx.Timeout(PORT_HTTP_TIMEOUT)
24
24
  PORT_HTTPX_LIMITS = httpx.Limits(
@@ -67,7 +67,7 @@ class IntegrationConfiguration(BaseOceanSettings, extra=Extra.allow):
67
67
  allow_environment_variables_jq_access: bool = True
68
68
  initialize_port_resources: bool = True
69
69
  scheduled_resync_interval: int | None = None
70
- client_timeout: int = 30
70
+ client_timeout: int = 60
71
71
  send_raw_data_examples: bool = True
72
72
  port: PortSettings
73
73
  event_listener: EventListenerSettingsType = Field(
@@ -106,18 +106,27 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
106
106
  should_raise=False,
107
107
  )
108
108
  else:
109
+ entities_with_search_identifier: list[Entity] = []
110
+ entities_without_search_identifier: list[Entity] = []
111
+ for entity in entities:
112
+ if entity.is_using_search_identifier:
113
+ entities_with_search_identifier.append(entity)
114
+ else:
115
+ entities_without_search_identifier.append(entity)
116
+
109
117
  ordered_created_entities = reversed(
110
- order_by_entities_dependencies(entities)
118
+ entities_with_search_identifier
119
+ + order_by_entities_dependencies(entities_without_search_identifier)
111
120
  )
112
121
  for entity in ordered_created_entities:
113
- modified_entities.append(
114
- await self.context.port_client.upsert_entity(
115
- entity,
116
- event.port_app_config.get_port_request_options(),
117
- user_agent_type,
118
- should_raise=False,
119
- )
122
+ upsertedEntity = await self.context.port_client.upsert_entity(
123
+ entity,
124
+ event.port_app_config.get_port_request_options(),
125
+ user_agent_type,
126
+ should_raise=False,
120
127
  )
128
+ if upsertedEntity:
129
+ modified_entities.append(upsertedEntity)
121
130
  return modified_entities
122
131
 
123
132
  async def delete(
@@ -13,18 +13,22 @@ class Rule(BaseModel):
13
13
  value: str
14
14
 
15
15
 
16
- class SearchRelation(BaseModel):
16
+ class IngestSearchQuery(BaseModel):
17
17
  combinator: str
18
- rules: list[Rule | SearchRelation]
18
+ rules: list[Rule | IngestSearchQuery]
19
19
 
20
20
 
21
21
  class EntityMapping(BaseModel):
22
- identifier: str
22
+ identifier: str | IngestSearchQuery
23
23
  title: str | None
24
24
  blueprint: str
25
25
  team: str | None
26
26
  properties: dict[str, str] = Field(default_factory=dict)
27
- relations: dict[str, str | SearchRelation] = Field(default_factory=dict)
27
+ relations: dict[str, str | IngestSearchQuery] = Field(default_factory=dict)
28
+
29
+ @property
30
+ def is_using_search_identifier(self) -> bool:
31
+ return isinstance(self.identifier, dict)
28
32
 
29
33
 
30
34
  class MappingsConfig(BaseModel):
@@ -154,6 +154,12 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
154
154
  results: list[RAW_ITEM],
155
155
  user_agent_type: UserAgentType,
156
156
  ) -> tuple[list[Entity], list[Exception]]:
157
+ if resource.port.entity.mappings.is_using_search_identifier:
158
+ logger.info(
159
+ f"Skip unregistering resource of kind {resource.kind}, as mapping defined with search identifier"
160
+ )
161
+ return [], []
162
+
157
163
  objects_diff = await self._calculate_raw([(resource, results)])
158
164
  entities_selector_diff, errors = objects_diff[0]
159
165
 
@@ -272,7 +278,8 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
272
278
  [
273
279
  entity
274
280
  for entity in entities_to_delete
275
- if (entity.identifier, entity.blueprint)
281
+ if not entity.is_using_search_identifier
282
+ and (entity.identifier, entity.blueprint)
276
283
  not in registered_entities_attributes
277
284
  ],
278
285
  )
port_ocean/core/models.py CHANGED
@@ -19,6 +19,10 @@ class Entity(BaseModel):
19
19
  properties: dict[str, Any] = {}
20
20
  relations: dict[str, Any] = {}
21
21
 
22
+ @property
23
+ def is_using_search_identifier(self) -> bool:
24
+ return isinstance(self.identifier, dict)
25
+
22
26
 
23
27
  class BlueprintRelation(BaseModel):
24
28
  many: bool
@@ -290,6 +290,11 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
290
290
  if remaining_attempts < 1:
291
291
  self._log_error(request, error)
292
292
  raise
293
+ except httpx.ReadTimeout as e:
294
+ error = e
295
+ if remaining_attempts < 1:
296
+ self._log_error(request, error)
297
+ raise
293
298
  except httpx.TimeoutException as e:
294
299
  error = e
295
300
  if remaining_attempts < 1:
port_ocean/middlewares.py CHANGED
@@ -52,7 +52,14 @@ async def request_handler(
52
52
  request_id = generate_uuid()
53
53
 
54
54
  with logger.contextualize(request_id=request_id):
55
- logger.bind(url=str(request.url), method=request.method).info("Request started")
55
+ log_level = (
56
+ "DEBUG"
57
+ if request.url.path == "/docs" or request.url.path == "/openapi.json"
58
+ else "INFO"
59
+ )
60
+ logger.bind(url=str(request.url), method=request.method).log(
61
+ log_level, f"Request to {request.url.path} started"
62
+ )
56
63
  response = await _handle_silently(call_next, request)
57
64
 
58
65
  end_time = get_time(seconds_precision=False)
@@ -61,5 +68,6 @@ async def request_handler(
61
68
  response.headers["X-Process-Time"] = str(time_elapsed)
62
69
  logger.bind(
63
70
  time_elapsed=time_elapsed, response_status=response.status_code
64
- ).info("Request ended")
71
+ ).log(log_level, f"Request to {request.url.path} ended")
72
+
65
73
  return response
@@ -0,0 +1,53 @@
1
+ from typing import Any
2
+ from unittest.mock import MagicMock
3
+
4
+ import pytest
5
+
6
+ from port_ocean.clients.port.mixins.entities import EntityClientMixin
7
+ from port_ocean.core.models import Entity
8
+ from httpx import ReadTimeout
9
+
10
+
11
+ errored_entity_identifier: str = "a"
12
+ expected_result_entities = [
13
+ Entity(identifier="b", blueprint="b"),
14
+ Entity(identifier="c", blueprint="c"),
15
+ ]
16
+ all_entities = [
17
+ Entity(identifier=errored_entity_identifier, blueprint="a")
18
+ ] + expected_result_entities
19
+
20
+
21
+ async def mock_upsert_entity(entity: Entity, *args: Any, **kwargs: Any) -> Entity:
22
+ if entity.identifier == errored_entity_identifier:
23
+ raise ReadTimeout("")
24
+ else:
25
+ return entity
26
+
27
+
28
+ @pytest.fixture
29
+ async def entity_client(monkeypatch: Any) -> EntityClientMixin:
30
+ # Arrange
31
+ entity_client = EntityClientMixin(auth=MagicMock(), client=MagicMock())
32
+ monkeypatch.setattr(entity_client, "upsert_entity", mock_upsert_entity)
33
+
34
+ return entity_client
35
+
36
+
37
+ async def test_batch_upsert_entities_read_timeout_should_raise_false(
38
+ entity_client: EntityClientMixin,
39
+ ) -> None:
40
+ result_entities = await entity_client.batch_upsert_entities(
41
+ entities=all_entities, request_options=MagicMock(), should_raise=False
42
+ )
43
+
44
+ assert result_entities == expected_result_entities
45
+
46
+
47
+ async def test_batch_upsert_entities_read_timeout_should_raise_true(
48
+ entity_client: EntityClientMixin,
49
+ ) -> None:
50
+ with pytest.raises(ReadTimeout):
51
+ await entity_client.batch_upsert_entities(
52
+ entities=all_entities, request_options=MagicMock(), should_raise=True
53
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.10.6
3
+ Version: 0.10.8
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
@@ -44,16 +44,16 @@ port_ocean/clients/port/authentication.py,sha256=t3z6h4vld-Tzkpth15sstaMJg0rccX-
44
44
  port_ocean/clients/port/client.py,sha256=Xd8Jk25Uh4WXY_WW-z1Qbv6F3ZTBFPoOolsxHMfozKw,3366
45
45
  port_ocean/clients/port/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  port_ocean/clients/port/mixins/blueprints.py,sha256=8ZVC5i8K1WKQMJJiPqZmgcOlF3OyxWz1aAQ_WA5UW3c,4500
47
- port_ocean/clients/port/mixins/entities.py,sha256=oyd1mVlaoRnQLMpN9oOuYOC2PqLTMfJ51Z7cNyuVNNc,8647
47
+ port_ocean/clients/port/mixins/entities.py,sha256=WdqT1gyS81pByUl9xIfZz_xEHRaBfDuZ-ekKX53oBSE,8870
48
48
  port_ocean/clients/port/mixins/integrations.py,sha256=HnWXaJt41SUcha-bhvLdJW07j-l7xIo91GUzzwl2f_E,4859
49
49
  port_ocean/clients/port/mixins/migrations.py,sha256=A6896oJF6WbFL2WroyTkMzr12yhVyWqGoq9dtLNSKBY,1457
50
50
  port_ocean/clients/port/retry_transport.py,sha256=PtIZOAZ6V-ncpVysRUsPOgt8Sf01QLnTKB5YeKBxkJk,1861
51
51
  port_ocean/clients/port/types.py,sha256=nvlgiAq4WH5_F7wQbz_GAWl-faob84LVgIjZ2Ww5mTk,451
52
- port_ocean/clients/port/utils.py,sha256=O9mBu6zp4TfpS4SQ3qCPpn9ZVyYF8GKnji4UnYhM3yg,2373
52
+ port_ocean/clients/port/utils.py,sha256=5B6rHgiVrtiL4YWh7Eq7_ncIeDwrDsB7jIvRik5xH8c,2373
53
53
  port_ocean/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  port_ocean/config/base.py,sha256=x1gFbzujrxn7EJudRT81C6eN9WsYAb3vOHwcpcpX8Tc,6370
55
55
  port_ocean/config/dynamic.py,sha256=qOFkRoJsn_BW7581omi_AoMxoHqasf_foxDQ_G11_SI,2030
56
- port_ocean/config/settings.py,sha256=ULv_n7Al94Vyw7Qo4pfbAay6_0sc56s9eAUoOPqVlPo,4274
56
+ port_ocean/config/settings.py,sha256=77MtPz-ZpOWjuuOcorAy4MSNuYhRE2a9DDBKx3sRrV8,4274
57
57
  port_ocean/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  port_ocean/consumers/kafka_consumer.py,sha256=N8KocjBi9aR0BOPG8hgKovg-ns_ggpEjrSxqSqF_BSo,4710
59
59
  port_ocean/context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -77,7 +77,7 @@ port_ocean/core/handlers/base.py,sha256=cTarblazu8yh8xz2FpB-dzDKuXxtoi143XJgPbV_
77
77
  port_ocean/core/handlers/entities_state_applier/__init__.py,sha256=kgLZDCeCEzi4r-0nzW9k78haOZNf6PX7mJOUr34A4c8,173
78
78
  port_ocean/core/handlers/entities_state_applier/base.py,sha256=5wHL0icfFAYRPqk8iV_wN49GdJ3aRUtO8tumSxBi4Wo,2268
79
79
  port_ocean/core/handlers/entities_state_applier/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
- port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=fZzaz8idbZQhYz_dqohNvoRWGdET_cYHZDenQmnkdCA,5438
80
+ port_ocean/core/handlers/entities_state_applier/port/applier.py,sha256=EirgWhT_TNeEwfdCElEDGkJ2tSOz9HsaUJ1i2uD7z28,5922
81
81
  port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py,sha256=1zncwCbE-Gej0xaWKlzZgoXxOBe9bgs_YxlZ8QW3NdI,1751
82
82
  port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py,sha256=82BvU8t5w9uhsxX8hbnwuRPuWhW3cMeuT_5sVIkip1I,1550
83
83
  port_ocean/core/handlers/entity_processor/__init__.py,sha256=FvFCunFg44wNQoqlybem9MthOs7p1Wawac87uSXz9U8,156
@@ -86,7 +86,7 @@ port_ocean/core/handlers/entity_processor/jq_entity_processor.py,sha256=_nQtlY0Z
86
86
  port_ocean/core/handlers/port_app_config/__init__.py,sha256=8AAT5OthiVM7KCcM34iEgEeXtn2pRMrT4Dze5r1Ixbk,134
87
87
  port_ocean/core/handlers/port_app_config/api.py,sha256=6VbKPwFzsWG0IYsVD81hxSmfqtHUFqrfUuj1DBX5g4w,853
88
88
  port_ocean/core/handlers/port_app_config/base.py,sha256=4Nxt2g8voEIHJ4Y1Km5NJcaG2iSbCklw5P8-Kus7Y9k,3007
89
- port_ocean/core/handlers/port_app_config/models.py,sha256=4dw6HbgjMG3advpN3x6XF35xsgScnWm0KKTERG4CYZ8,2201
89
+ port_ocean/core/handlers/port_app_config/models.py,sha256=YvYtf_44KD_rN4xK-3xHtdpRZ1M8Qo-m9K4LDtH7FYU,2344
90
90
  port_ocean/core/handlers/resync_state_updater/__init__.py,sha256=kG6y-JQGpPfuTHh912L_bctIDCzAK4DN-d00S7rguWU,81
91
91
  port_ocean/core/handlers/resync_state_updater/updater.py,sha256=Yg9ET6ZV5B9GW7u6zZA6GlB_71kmvxvYX2FWgQNzMvo,3182
92
92
  port_ocean/core/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -95,9 +95,9 @@ port_ocean/core/integrations/mixins/__init__.py,sha256=FA1FEKMM6P-L2_m7Q4L20mFa4
95
95
  port_ocean/core/integrations/mixins/events.py,sha256=Ddfx2L4FpghV38waF8OfVeOV0bHBxNIgjU-q5ffillI,2341
96
96
  port_ocean/core/integrations/mixins/handler.py,sha256=mZ7-0UlG3LcrwJttFbMe-R4xcOU2H_g33tZar7PwTv8,3771
97
97
  port_ocean/core/integrations/mixins/sync.py,sha256=B9fEs8faaYLLikH9GBjE_E61vo0bQDjIGQsQ1SRXOlA,3931
98
- port_ocean/core/integrations/mixins/sync_raw.py,sha256=2vyHhGWyPchVfSUKRRfSJ2XsX55ygLWCFrKDaqkNd0o,18386
98
+ port_ocean/core/integrations/mixins/sync_raw.py,sha256=nPYlUTQdh6HDIqBwh3jQ-hmraNx4CeSK9YQ98NTonbQ,18697
99
99
  port_ocean/core/integrations/mixins/utils.py,sha256=7y1rGETZIjOQadyIjFJXIHKkQFKx_SwiP-TrAIsyyLY,2303
100
- port_ocean/core/models.py,sha256=8yYyF4DQ4jMmQJPsmh5-XoF9gfHUTuBit6zuT-bxZ7Y,1228
100
+ port_ocean/core/models.py,sha256=dJ2_olTdbjUpObQJNmg7e7EENU_zZiX6XOaknNp54B0,1342
101
101
  port_ocean/core/ocean_types.py,sha256=3_d8-n626f1kWLQ_Jxw194LEyrOVupz05qs_Y1pvB-A,990
102
102
  port_ocean/core/utils.py,sha256=40UjRauRJO47WDSNn9bkCRD2bfhfB3e-dnOLULnuVzE,3631
103
103
  port_ocean/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -110,17 +110,18 @@ port_ocean/exceptions/port_defaults.py,sha256=pJ5im06ziDX2IVstSlYIjVHhs4JW-N0Vr0
110
110
  port_ocean/exceptions/utils.py,sha256=gjOqpi-HpY1l4WlMFsGA9yzhxDhajhoGGdDDyGbLnqI,197
111
111
  port_ocean/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
112
  port_ocean/helpers/async_client.py,sha256=SRlP6o7_FCSY3UHnRlZdezppePVxxOzZ0z861vE3K40,1783
113
- port_ocean/helpers/retry.py,sha256=hcfVmt2Fp53EQViYiOWA-FTWD80jvS-mWKhwMHiBM7Q,14869
113
+ port_ocean/helpers/retry.py,sha256=IQ0RfQ2T5o6uoZh2WW2nrFH5TT6K_k3y2Im0HDp5j9Y,15059
114
114
  port_ocean/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
115
  port_ocean/log/handlers.py,sha256=k9G_Mb4ga2-Jke9irpdlYqj6EYiwv0gEsh4TgyqqOmI,2853
116
116
  port_ocean/log/logger_setup.py,sha256=BaXt-mh9CVXhneh37H46d04lqOdIBixG1pFyGfotuZs,2328
117
117
  port_ocean/log/sensetive.py,sha256=wkyvkKMbyLTjZDSbvvLHL9bv4RvD0DPAyL3uWSttUOA,2916
118
- port_ocean/middlewares.py,sha256=6GrhldYAazxSwK2TbS-J28XdZ-9wO3PgCcyIMhnnJvI,2480
118
+ port_ocean/middlewares.py,sha256=9wYCdyzRZGK1vjEJ28FY_DkfwDNENmXp504UKPf5NaQ,2727
119
119
  port_ocean/ocean.py,sha256=0dtaiRxKgY3kYNm1ZI3uFCJRhnNoTMwVv-PkWlLJQrQ,4842
120
120
  port_ocean/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
121
  port_ocean/run.py,sha256=rTxBlrQd4yyrtgErCFJCHCEHs7d1OXrRiJehUYmIbN0,2212
122
122
  port_ocean/sonar-project.properties,sha256=X_wLzDOkEVmpGLRMb2fg9Rb0DxWwUFSvESId8qpvrPI,73
123
123
  port_ocean/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
+ port_ocean/tests/clients/port/mixins/test_entities.py,sha256=A9myrnkLhKSQrnOLv1Zz2wiOVSxW65Q9RIUIRbn_V7w,1586
124
125
  port_ocean/tests/helpers/__init__.py,sha256=XQBdAi54t9VavF11dGVLeS1jSoEKRjqRemKskLh-nSo,2377
125
126
  port_ocean/tests/test_sample.py,sha256=Ew5LA_G1k6DC5a2ygU2FoyjZQa0fRmPy73N0bio0d14,46
126
127
  port_ocean/utils/__init__.py,sha256=KMGnCPXZJbNwtgxtyMycapkDz8tpSyw23MSYT3iVeHs,91
@@ -133,8 +134,8 @@ port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,32
133
134
  port_ocean/utils/signal.py,sha256=K-6kKFQTltcmKDhtyZAcn0IMa3sUpOHGOAUdWKgx0_E,1369
134
135
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
135
136
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
136
- port_ocean-0.10.6.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
137
- port_ocean-0.10.6.dist-info/METADATA,sha256=bU-QFO_CsBFDwaEVaDqlBctyNMbrlIM7DGbjyhh-JVU,6616
138
- port_ocean-0.10.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
139
- port_ocean-0.10.6.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
140
- port_ocean-0.10.6.dist-info/RECORD,,
137
+ port_ocean-0.10.8.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
138
+ port_ocean-0.10.8.dist-info/METADATA,sha256=RkH8PbV-aHXG36GanolJDtpHUZtdjtVn4_8cIId-St8,6616
139
+ port_ocean-0.10.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
140
+ port_ocean-0.10.8.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
141
+ port_ocean-0.10.8.dist-info/RECORD,,