port-ocean 0.12.6__py3-none-any.whl → 0.12.7.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.
- port_ocean/context/ocean.py +2 -2
- port_ocean/core/event_listener/http.py +4 -5
- port_ocean/exceptions/api.py +1 -1
- port_ocean/middlewares.py +52 -55
- port_ocean/ocean.py +25 -12
- port_ocean/tests/helpers/ocean_app.py +5 -2
- {port_ocean-0.12.6.dist-info → port_ocean-0.12.7.dev1.dist-info}/METADATA +1 -1
- {port_ocean-0.12.6.dist-info → port_ocean-0.12.7.dev1.dist-info}/RECORD +11 -11
- {port_ocean-0.12.6.dist-info → port_ocean-0.12.7.dev1.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.12.6.dist-info → port_ocean-0.12.7.dev1.dist-info}/WHEEL +0 -0
- {port_ocean-0.12.6.dist-info → port_ocean-0.12.7.dev1.dist-info}/entry_points.txt +0 -0
port_ocean/context/ocean.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from typing import Callable, TYPE_CHECKING, Any, Literal, Union
|
|
2
2
|
|
|
3
|
-
from fastapi import APIRouter
|
|
4
3
|
from pydantic.main import BaseModel
|
|
4
|
+
from starlette.routing import Router
|
|
5
5
|
from werkzeug.local import LocalProxy
|
|
6
6
|
|
|
7
7
|
from port_ocean.clients.port.types import UserAgentType
|
|
@@ -45,7 +45,7 @@ class PortOceanContext:
|
|
|
45
45
|
return self.app.config
|
|
46
46
|
|
|
47
47
|
@property
|
|
48
|
-
def router(self) ->
|
|
48
|
+
def router(self) -> Router:
|
|
49
49
|
return self.app.integration_router
|
|
50
50
|
|
|
51
51
|
@property
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from typing import Literal, Any
|
|
2
2
|
|
|
3
|
-
from fastapi import APIRouter
|
|
4
3
|
from loguru import logger
|
|
5
4
|
from pydantic import AnyHttpUrl
|
|
6
5
|
from pydantic.fields import Field
|
|
6
|
+
from starlette.requests import Request
|
|
7
|
+
from starlette.routing import Router, Route
|
|
7
8
|
|
|
8
9
|
from port_ocean.context.ocean import ocean
|
|
9
10
|
from port_ocean.core.event_listener.base import (
|
|
@@ -60,10 +61,8 @@ class HttpEventListener(BaseEventListener):
|
|
|
60
61
|
It sets up an APIRouter to handle the `/resync` endpoint and registers the "on_resync" event handler.
|
|
61
62
|
"""
|
|
62
63
|
logger.info("Setting up HTTP Event Listener")
|
|
63
|
-
target_channel_router = APIRouter()
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
async def resync() -> None:
|
|
65
|
+
async def resync(request: Request) -> None:
|
|
67
66
|
await self._resync({})
|
|
68
67
|
|
|
69
|
-
ocean.
|
|
68
|
+
ocean.router.routes.append(Route("/resync", methods=["post"], endpoint=resync))
|
port_ocean/exceptions/api.py
CHANGED
port_ocean/middlewares.py
CHANGED
|
@@ -1,73 +1,70 @@
|
|
|
1
|
-
from typing import Callable, Awaitable
|
|
2
|
-
|
|
3
|
-
from fastapi import Request, Response
|
|
4
1
|
from loguru import logger
|
|
2
|
+
from starlette.requests import Request
|
|
3
|
+
from starlette.responses import Response
|
|
5
4
|
|
|
6
5
|
from port_ocean.exceptions.api import BaseAPIException, InternalServerException
|
|
7
6
|
from .context.event import event_context, EventType
|
|
8
7
|
from .context.ocean import ocean
|
|
9
8
|
from .utils.misc import get_time, generate_uuid
|
|
9
|
+
from starlette.middleware.base import (
|
|
10
|
+
BaseHTTPMiddleware,
|
|
11
|
+
RequestResponseEndpoint,
|
|
12
|
+
)
|
|
10
13
|
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
if request.url.path.startswith("/integration"):
|
|
18
|
-
async with event_context(EventType.HTTP_REQUEST, trigger_type="request"):
|
|
19
|
-
await ocean.integration.port_app_config_handler.get_port_app_config()
|
|
20
|
-
response = await call_next(request)
|
|
21
|
-
else:
|
|
22
|
-
response = await call_next(request)
|
|
15
|
+
class RequestHandlerMiddleware(BaseHTTPMiddleware):
|
|
16
|
+
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
|
|
17
|
+
start_time = get_time(seconds_precision=False)
|
|
18
|
+
request_id = generate_uuid()
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
with logger.contextualize(request_id=request_id):
|
|
21
|
+
log_level = (
|
|
22
|
+
"DEBUG"
|
|
23
|
+
if request.url.path == "/docs" or request.url.path == "/openapi.json"
|
|
24
|
+
else "INFO"
|
|
29
25
|
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"Request did not succeed due to server-side error"
|
|
26
|
+
logger.bind(url=str(request.url), method=request.method).log(
|
|
27
|
+
log_level, f"Request to {request.url.path} started"
|
|
33
28
|
)
|
|
29
|
+
response = await self._handle_silently(request, call_next)
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
end_time = get_time(seconds_precision=False)
|
|
32
|
+
time_elapsed = round(end_time - start_time, 5)
|
|
33
|
+
response.headers["X-Request-ID"] = request_id
|
|
34
|
+
response.headers["X-Process-Time"] = str(time_elapsed)
|
|
35
|
+
logger.bind(
|
|
36
|
+
time_elapsed=time_elapsed, response_status=response.status_code
|
|
37
|
+
).log(log_level, f"Request to {request.url.path} ended")
|
|
40
38
|
|
|
39
|
+
return response
|
|
41
40
|
|
|
42
|
-
async def
|
|
43
|
-
|
|
44
|
-
) -> Response:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
async def _handle_silently(
|
|
42
|
+
self, request: Request, call_next: RequestResponseEndpoint
|
|
43
|
+
) -> Response:
|
|
44
|
+
response: Response
|
|
45
|
+
try:
|
|
46
|
+
if request.url.path.startswith("/integration"):
|
|
47
|
+
async with event_context(
|
|
48
|
+
EventType.HTTP_REQUEST, trigger_type="request"
|
|
49
|
+
):
|
|
50
|
+
await ocean.integration.port_app_config_handler.get_port_app_config()
|
|
51
|
+
response = await call_next(request)
|
|
52
|
+
else:
|
|
53
|
+
response = await call_next(request)
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
except BaseAPIException as ex:
|
|
56
|
+
response = ex.response()
|
|
57
|
+
if response.status_code < 500:
|
|
58
|
+
logger.bind(exception=str(ex)).info(
|
|
59
|
+
"Request did not succeed due to client-side error"
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
logger.opt(exception=True).warning(
|
|
63
|
+
"Request did not succeed due to server-side error"
|
|
64
|
+
)
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
response.headers["X-Process-Time"] = str(time_elapsed)
|
|
69
|
-
logger.bind(
|
|
70
|
-
time_elapsed=time_elapsed, response_status=response.status_code
|
|
71
|
-
).log(log_level, f"Request to {request.url.path} ended")
|
|
66
|
+
except Exception:
|
|
67
|
+
logger.opt(exception=True).error("Request failed due to unexpected error")
|
|
68
|
+
response = InternalServerException().response()
|
|
72
69
|
|
|
73
70
|
return response
|
port_ocean/ocean.py
CHANGED
|
@@ -4,9 +4,13 @@ import threading
|
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
5
|
from typing import Callable, Any, Dict, AsyncIterator, Type
|
|
6
6
|
|
|
7
|
-
from fastapi import FastAPI, APIRouter
|
|
8
7
|
from loguru import logger
|
|
9
8
|
from pydantic import BaseModel
|
|
9
|
+
from starlette.applications import Starlette
|
|
10
|
+
from starlette.middleware import Middleware
|
|
11
|
+
from starlette.requests import Request
|
|
12
|
+
from starlette.responses import JSONResponse
|
|
13
|
+
from starlette.routing import Route, Mount, Router
|
|
10
14
|
from starlette.types import Scope, Receive, Send
|
|
11
15
|
|
|
12
16
|
from port_ocean.core.handlers.resync_state_updater import ResyncStateUpdater
|
|
@@ -22,7 +26,7 @@ from port_ocean.context.ocean import (
|
|
|
22
26
|
)
|
|
23
27
|
from port_ocean.core.integrations.base import BaseIntegration
|
|
24
28
|
from port_ocean.log.sensetive import sensitive_log_filter
|
|
25
|
-
from port_ocean.middlewares import
|
|
29
|
+
from port_ocean.middlewares import RequestHandlerMiddleware
|
|
26
30
|
from port_ocean.utils.repeat import repeat_every
|
|
27
31
|
from port_ocean.utils.signal import signal_handler
|
|
28
32
|
from port_ocean.version import __integration_version__
|
|
@@ -32,15 +36,16 @@ from port_ocean.utils.misc import IntegrationStateStatus
|
|
|
32
36
|
class Ocean:
|
|
33
37
|
def __init__(
|
|
34
38
|
self,
|
|
35
|
-
app:
|
|
39
|
+
app: Starlette | None = None,
|
|
36
40
|
integration_class: Callable[[PortOceanContext], BaseIntegration] | None = None,
|
|
37
|
-
integration_router:
|
|
41
|
+
integration_router: Router | None = None,
|
|
38
42
|
config_factory: Type[BaseModel] | None = None,
|
|
39
43
|
config_override: Dict[str, Any] | None = None,
|
|
40
44
|
):
|
|
41
45
|
initialize_port_ocean_context(self)
|
|
42
|
-
|
|
43
|
-
self.
|
|
46
|
+
|
|
47
|
+
self.starlette_app = app or Starlette()
|
|
48
|
+
self.integration_router = integration_router or Router()
|
|
44
49
|
|
|
45
50
|
self.config = IntegrationConfiguration(
|
|
46
51
|
# type: ignore
|
|
@@ -52,7 +57,6 @@ class Ocean:
|
|
|
52
57
|
sensitive_log_filter.hide_sensitive_strings(
|
|
53
58
|
*self.config.get_sensitive_fields_data()
|
|
54
59
|
)
|
|
55
|
-
self.integration_router = integration_router or APIRouter()
|
|
56
60
|
|
|
57
61
|
self.port_client = PortClient(
|
|
58
62
|
base_url=self.config.port.base_url,
|
|
@@ -113,10 +117,8 @@ class Ocean:
|
|
|
113
117
|
await repeated_function()
|
|
114
118
|
|
|
115
119
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
116
|
-
self.fast_api_app.include_router(self.integration_router, prefix="/integration")
|
|
117
|
-
|
|
118
120
|
@asynccontextmanager
|
|
119
|
-
async def
|
|
121
|
+
async def lifespan(_: Starlette) -> AsyncIterator[None]:
|
|
120
122
|
try:
|
|
121
123
|
await self.integration.start()
|
|
122
124
|
await self._setup_scheduled_resync()
|
|
@@ -127,5 +129,16 @@ class Ocean:
|
|
|
127
129
|
finally:
|
|
128
130
|
signal_handler.exit()
|
|
129
131
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
async def health(request: Request) -> JSONResponse:
|
|
133
|
+
return JSONResponse({"ok": True})
|
|
134
|
+
|
|
135
|
+
self.starlette_app = Starlette(
|
|
136
|
+
routes=[
|
|
137
|
+
Route("/docs", endpoint=health),
|
|
138
|
+
Mount("/integration", routes=self.integration_router.routes),
|
|
139
|
+
],
|
|
140
|
+
middleware=[Middleware(RequestHandlerMiddleware)],
|
|
141
|
+
lifespan=lifespan,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
await self.starlette_app(scope, receive, send)
|
|
@@ -6,6 +6,7 @@ from typing import Any, Dict, List, Tuple, Union
|
|
|
6
6
|
from yaml import safe_load
|
|
7
7
|
|
|
8
8
|
from port_ocean.bootstrap import create_default_app
|
|
9
|
+
from port_ocean.config.dynamic import default_config_factory
|
|
9
10
|
from port_ocean.core.handlers.port_app_config.models import ResourceConfig
|
|
10
11
|
from port_ocean.core.ocean_types import RESYNC_RESULT
|
|
11
12
|
from port_ocean.ocean import Ocean
|
|
@@ -16,8 +17,9 @@ def get_integration_ocean_app(
|
|
|
16
17
|
integration_path: str, config_overrides: Union[Dict[str, Any], None] = None
|
|
17
18
|
) -> Ocean:
|
|
18
19
|
spec_file = get_spec_file(Path(integration_path))
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
config_factory = None
|
|
21
|
+
if spec_file is not None:
|
|
22
|
+
config_factory = default_config_factory(spec_file.get("configurations", []))
|
|
21
23
|
|
|
22
24
|
default_app = create_default_app(
|
|
23
25
|
integration_path,
|
|
@@ -32,6 +34,7 @@ def get_integration_ocean_app(
|
|
|
32
34
|
},
|
|
33
35
|
},
|
|
34
36
|
)
|
|
37
|
+
|
|
35
38
|
main_path = f"{integration_path}/main.py"
|
|
36
39
|
sys.path.append(integration_path)
|
|
37
40
|
app_module = load_module(main_path)
|
|
@@ -57,7 +57,7 @@ port_ocean/consumers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
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
|
|
59
59
|
port_ocean/context/event.py,sha256=WduGbCPgm2J2a63EY4J3XWwFGSt3ja1acBVpyI_ciMo,5430
|
|
60
|
-
port_ocean/context/ocean.py,sha256=
|
|
60
|
+
port_ocean/context/ocean.py,sha256=UDlsk6CUCRLb0Oc01piU3CdtG9LpBcw-yswk8DtiDAw,4661
|
|
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
|
|
@@ -67,7 +67,7 @@ port_ocean/core/defaults/initialize.py,sha256=qg4JLIWjp0c5-9w09X99muHRYX4k1cGZ_7
|
|
|
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
|
|
70
|
-
port_ocean/core/event_listener/http.py,sha256=
|
|
70
|
+
port_ocean/core/event_listener/http.py,sha256=ByO0puywx-NagVqpHwWKv1NldgWqkjTgwCGmkR1Et2k,2562
|
|
71
71
|
port_ocean/core/event_listener/kafka.py,sha256=ulidnp4sz-chXwHsbH9JayVjcxy_mG6ts_Im3YKmLpI,6983
|
|
72
72
|
port_ocean/core/event_listener/once.py,sha256=iL3NkujZOw-7LpxT-EAUJUcAuiAZPm4ZzjHTSt9EdHs,5918
|
|
73
73
|
port_ocean/core/event_listener/polling.py,sha256=d9E3oRLy-Ogb0oadZNxSDgSLIHe4z92uMVwztscZycg,3667
|
|
@@ -100,7 +100,7 @@ port_ocean/core/models.py,sha256=dJ2_olTdbjUpObQJNmg7e7EENU_zZiX6XOaknNp54B0,134
|
|
|
100
100
|
port_ocean/core/ocean_types.py,sha256=3_d8-n626f1kWLQ_Jxw194LEyrOVupz05qs_Y1pvB-A,990
|
|
101
101
|
port_ocean/core/utils.py,sha256=40UjRauRJO47WDSNn9bkCRD2bfhfB3e-dnOLULnuVzE,3631
|
|
102
102
|
port_ocean/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
103
|
-
port_ocean/exceptions/api.py,sha256=
|
|
103
|
+
port_ocean/exceptions/api.py,sha256=gb2jqBfRv21-Y5BQ8-1DmYMT7uv7QEhhNDvpZCvqdKU,428
|
|
104
104
|
port_ocean/exceptions/base.py,sha256=uY4DX7fIITDFfemCJDWpaZi3bD51lcANc5swpoNvMJA,46
|
|
105
105
|
port_ocean/exceptions/clients.py,sha256=LKLLs-Zy3caNG85rwxfOw2rMr8qqVV6SHUq4fRCZ99U,180
|
|
106
106
|
port_ocean/exceptions/context.py,sha256=mA8HII6Rl4QxKUz98ppy1zX3kaziaen21h1ZWuU3ADc,372
|
|
@@ -114,8 +114,8 @@ port_ocean/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
114
114
|
port_ocean/log/handlers.py,sha256=k9G_Mb4ga2-Jke9irpdlYqj6EYiwv0gEsh4TgyqqOmI,2853
|
|
115
115
|
port_ocean/log/logger_setup.py,sha256=BaXt-mh9CVXhneh37H46d04lqOdIBixG1pFyGfotuZs,2328
|
|
116
116
|
port_ocean/log/sensetive.py,sha256=lVKiZH6b7TkrZAMmhEJRhcl67HNM94e56x12DwFgCQk,2920
|
|
117
|
-
port_ocean/middlewares.py,sha256=
|
|
118
|
-
port_ocean/ocean.py,sha256=
|
|
117
|
+
port_ocean/middlewares.py,sha256=K_FGt39YgiC0397W3ON1Z0n0bRIke95sZkw7a0xOiII,2737
|
|
118
|
+
port_ocean/ocean.py,sha256=bxxcCAWKjz6j6lCEN40OEH4gG1eTqoQeeUpJi2gXLs4,5468
|
|
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
|
|
@@ -126,7 +126,7 @@ port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py,sha2
|
|
|
126
126
|
port_ocean/tests/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
127
127
|
port_ocean/tests/helpers/fixtures.py,sha256=IQEplbHhRgjrAsZlnXrgSYA5YQEn25I9HgO3_Fjibxg,1481
|
|
128
128
|
port_ocean/tests/helpers/integration.py,sha256=_RxS-RHpu11lrbhUXYPZp862HLWx8AoD7iZM6iXN8rs,1104
|
|
129
|
-
port_ocean/tests/helpers/ocean_app.py,sha256=
|
|
129
|
+
port_ocean/tests/helpers/ocean_app.py,sha256=8BysIhNqtTwhjnya5rr0AtrjulfJnJJMFz5cPUxIpLk,2167
|
|
130
130
|
port_ocean/tests/helpers/port_client.py,sha256=5d6GNr8vNNSOkrz1AdOhxBUKuusr_-UPDP7AVpHasQw,599
|
|
131
131
|
port_ocean/tests/helpers/smoke_test.py,sha256=_9aJJFRfuGJEg2D2YQJVJRmpreS6gEPHHQq8Q01x4aQ,2697
|
|
132
132
|
port_ocean/tests/test_smoke.py,sha256=uix2uIg_yOm8BHDgHw2hTFPy1fiIyxBGW3ENU_KoFlo,2557
|
|
@@ -141,8 +141,8 @@ port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,32
|
|
|
141
141
|
port_ocean/utils/signal.py,sha256=K-6kKFQTltcmKDhtyZAcn0IMa3sUpOHGOAUdWKgx0_E,1369
|
|
142
142
|
port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
|
|
143
143
|
port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
|
|
144
|
-
port_ocean-0.12.
|
|
145
|
-
port_ocean-0.12.
|
|
146
|
-
port_ocean-0.12.
|
|
147
|
-
port_ocean-0.12.
|
|
148
|
-
port_ocean-0.12.
|
|
144
|
+
port_ocean-0.12.7.dev1.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
145
|
+
port_ocean-0.12.7.dev1.dist-info/METADATA,sha256=EC1_Ph-D2G_JiDaYm3Ex28ZejTgQHe4zJH7C7XKj5tc,6674
|
|
146
|
+
port_ocean-0.12.7.dev1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
147
|
+
port_ocean-0.12.7.dev1.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
|
|
148
|
+
port_ocean-0.12.7.dev1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|