port-ocean 0.12.2.dev22__py3-none-any.whl → 0.12.4__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 +9 -17
- port_ocean/cli/cookiecutter/hooks/post_gen_project.py +10 -4
- port_ocean/context/ocean.py +2 -2
- port_ocean/core/integrations/base.py +2 -3
- port_ocean/core/integrations/mixins/sync_raw.py +56 -56
- port_ocean/exceptions/api.py +1 -1
- port_ocean/log/logger_setup.py +2 -2
- port_ocean/middlewares.py +55 -52
- port_ocean/ocean.py +24 -36
- port_ocean/tests/helpers/fixtures.py +3 -3
- port_ocean/tests/helpers/ocean_app.py +10 -5
- port_ocean/tests/utils/test_async_iterators.py +45 -0
- port_ocean/utils/async_iterators.py +60 -0
- {port_ocean-0.12.2.dev22.dist-info → port_ocean-0.12.4.dist-info}/METADATA +3 -4
- {port_ocean-0.12.2.dev22.dist-info → port_ocean-0.12.4.dist-info}/RECORD +18 -17
- {port_ocean-0.12.2.dev22.dist-info → port_ocean-0.12.4.dist-info}/WHEEL +1 -1
- {port_ocean-0.12.2.dev22.dist-info → port_ocean-0.12.4.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.12.2.dev22.dist-info → port_ocean-0.12.4.dist-info}/entry_points.txt +0 -0
port_ocean/cli/commands/new.py
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
|
-
import click
|
|
4
3
|
import json
|
|
5
|
-
from cookiecutter.main import cookiecutter # type: ignore
|
|
6
4
|
import os
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def symlink_makefile(result: str, name: str) -> None:
|
|
13
|
-
infra_make_file = os.path.join(
|
|
14
|
-
os.path.dirname(result), "../integrations/_infra/Makefile"
|
|
15
|
-
)
|
|
16
|
-
target_link_make_file = os.path.join(
|
|
17
|
-
os.path.dirname(result), f"../integrations/{name}/Makefile"
|
|
18
|
-
)
|
|
6
|
+
import click
|
|
7
|
+
from cookiecutter.main import cookiecutter # type: ignore
|
|
19
8
|
|
|
20
|
-
|
|
9
|
+
from port_ocean.cli.commands.main import cli_start, console, print_logo
|
|
10
|
+
from port_ocean.cli.utils import cli_root_path
|
|
21
11
|
|
|
22
12
|
|
|
23
13
|
def add_vscode_configuration(result: str, name: str) -> None:
|
|
@@ -73,11 +63,13 @@ def new(path: str, is_private_integration: bool) -> None:
|
|
|
73
63
|
"is_private_integration": is_private_integration,
|
|
74
64
|
},
|
|
75
65
|
)
|
|
66
|
+
|
|
76
67
|
name = result.split("/")[-1]
|
|
77
68
|
|
|
78
|
-
|
|
69
|
+
final_private_integration = os.path.exists(os.path.join(result, "Dockerfile"))
|
|
70
|
+
|
|
71
|
+
if not final_private_integration:
|
|
79
72
|
add_vscode_configuration(result, name)
|
|
80
|
-
symlink_makefile(result, name)
|
|
81
73
|
|
|
82
74
|
console.print(
|
|
83
75
|
"\n🌊 Ahoy, Captain! Your project is ready to set sail into the vast ocean of possibilities!",
|
|
@@ -95,7 +87,7 @@ def new(path: str, is_private_integration: bool) -> None:
|
|
|
95
87
|
"⚓️ Set sail with [blue]Ocean[/blue]: Run [bold][blue]ocean sail[/blue] <path_to_integration>[/bold] to run the project using Ocean.\n"
|
|
96
88
|
f"▶️ [bold][blue]ocean sail {path}/{name}[/blue][/bold] \n"
|
|
97
89
|
)
|
|
98
|
-
if not
|
|
90
|
+
if not final_private_integration:
|
|
99
91
|
console.print(
|
|
100
92
|
"⚓️ Smooth sailing with [blue]Make[/blue]: Alternatively, you can run [bold][blue]make run[/blue][/bold] to launch your project using Make. \n"
|
|
101
93
|
f"▶️ [bold][blue]make run {path}/{name}[/blue][/bold]"
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import shutil
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def handle_private_integration_flags():
|
|
5
|
-
|
|
6
|
+
infra_make_file = "../_infra/Makefile"
|
|
7
|
+
target_link_make_file = os.path.join("./Makefile")
|
|
8
|
+
|
|
6
9
|
if "{{ cookiecutter.is_private_integration }}" == "True":
|
|
10
|
+
shutil.copyfile(infra_make_file, target_link_make_file)
|
|
7
11
|
os.remove("sonar-project.properties")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
os.symlink(infra_make_file, target_link_make_file)
|
|
15
|
+
os.remove("Dockerfile")
|
|
16
|
+
os.remove(".dockerignore")
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
if __name__ == "__main__":
|
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
|
|
3
4
|
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) -> APIRouter:
|
|
49
49
|
return self.app.integration_router
|
|
50
50
|
|
|
51
51
|
@property
|
|
@@ -77,6 +77,5 @@ class BaseIntegration(SyncRawMixin, SyncMixin):
|
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
logger.info("Initializing event listener")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
event_listener = await self.event_listener_factory.create_event_listener()
|
|
81
|
+
await event_listener.start()
|
|
@@ -3,6 +3,7 @@ import inspect
|
|
|
3
3
|
import typing
|
|
4
4
|
from typing import Callable, Awaitable, Any
|
|
5
5
|
|
|
6
|
+
import httpx
|
|
6
7
|
from loguru import logger
|
|
7
8
|
|
|
8
9
|
from port_ocean.clients.port.types import UserAgentType
|
|
@@ -172,7 +173,6 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
172
173
|
self, resource_config: ResourceConfig, user_agent_type: UserAgentType
|
|
173
174
|
) -> tuple[list[Entity], list[Exception]]:
|
|
174
175
|
results, errors = await self._get_resource_raw_results(resource_config)
|
|
175
|
-
passed_entities = []
|
|
176
176
|
async_generators: list[ASYNC_GENERATOR_RESYNC_TYPE] = []
|
|
177
177
|
raw_results: RAW_RESULT = []
|
|
178
178
|
for result in results:
|
|
@@ -201,14 +201,14 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
201
201
|
0, send_raw_data_examples_amount - len(passed_entities)
|
|
202
202
|
)
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
204
|
+
entities, register_errors = await self._register_resource_raw(
|
|
205
|
+
resource_config,
|
|
206
|
+
items,
|
|
207
|
+
user_agent_type,
|
|
208
|
+
send_raw_data_examples_amount=send_raw_data_examples_amount,
|
|
209
|
+
)
|
|
210
|
+
errors.extend(register_errors)
|
|
211
|
+
passed_entities.extend(entities.passed)
|
|
212
212
|
except* OceanAbortException as error:
|
|
213
213
|
errors.append(error)
|
|
214
214
|
|
|
@@ -426,20 +426,20 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
426
426
|
use_cache=False
|
|
427
427
|
)
|
|
428
428
|
logger.info(f"Resync will use the following mappings: {app_config.dict()}")
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
429
|
+
try:
|
|
430
|
+
did_fetched_current_state = True
|
|
431
|
+
entities_at_port = await ocean.port_client.search_entities(
|
|
432
|
+
user_agent_type
|
|
433
|
+
)
|
|
434
|
+
except httpx.HTTPError as e:
|
|
435
|
+
logger.warning(
|
|
436
|
+
"Failed to fetch the current state of entities at Port. "
|
|
437
|
+
"Skipping delete phase due to unknown initial state. "
|
|
438
|
+
f"Error: {e}\n"
|
|
439
|
+
f"Response status code: {e.response.status_code if isinstance(e, httpx.HTTPStatusError) else None}\n"
|
|
440
|
+
f"Response content: {e.response.text if isinstance(e, httpx.HTTPStatusError) else None}\n"
|
|
441
|
+
)
|
|
442
|
+
did_fetched_current_state = False
|
|
443
443
|
|
|
444
444
|
creation_results: list[tuple[list[Entity], list[Exception]]] = []
|
|
445
445
|
|
|
@@ -458,36 +458,36 @@ class SyncRawMixin(HandlerMixin, EventsMixin):
|
|
|
458
458
|
except asyncio.CancelledError as e:
|
|
459
459
|
logger.warning("Resync aborted successfully, skipping delete phase. This leads to an incomplete state")
|
|
460
460
|
raise
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
461
|
+
else:
|
|
462
|
+
if not did_fetched_current_state:
|
|
463
|
+
logger.warning(
|
|
464
|
+
"Due to an error before the resync, the previous state of entities at Port is unknown."
|
|
465
|
+
" Skipping delete phase due to unknown initial state."
|
|
466
|
+
)
|
|
467
|
+
return
|
|
468
|
+
|
|
469
|
+
logger.info("Starting resync diff calculation")
|
|
470
|
+
flat_created_entities, errors = zip_and_sum(creation_results) or [
|
|
471
|
+
[],
|
|
472
|
+
[],
|
|
473
|
+
]
|
|
474
|
+
|
|
475
|
+
if errors:
|
|
476
|
+
message = f"Resync failed with {len(errors)}. Skipping delete phase due to incomplete state"
|
|
477
|
+
error_group = ExceptionGroup(
|
|
478
|
+
f"Resync failed with {len(errors)}. Skipping delete phase due to incomplete state",
|
|
479
|
+
errors,
|
|
480
|
+
)
|
|
481
|
+
if not silent:
|
|
482
|
+
raise error_group
|
|
483
|
+
|
|
484
|
+
logger.error(message, exc_info=error_group)
|
|
485
|
+
else:
|
|
486
|
+
logger.info(
|
|
487
|
+
f"Running resync diff calculation, number of entities at Port before resync: {len(entities_at_port)}, number of entities created during sync: {len(flat_created_entities)}"
|
|
488
|
+
)
|
|
489
|
+
await self.entities_state_applier.delete_diff(
|
|
490
|
+
{"before": entities_at_port, "after": flat_created_entities},
|
|
491
|
+
user_agent_type,
|
|
492
|
+
)
|
|
493
|
+
logger.info("Resync finished successfully")
|
port_ocean/exceptions/api.py
CHANGED
port_ocean/log/logger_setup.py
CHANGED
|
@@ -15,8 +15,8 @@ from port_ocean.utils.signal import signal_handler
|
|
|
15
15
|
def setup_logger(level: LogLevelType, enable_http_handler: bool) -> None:
|
|
16
16
|
logger.remove()
|
|
17
17
|
_stdout_loguru_handler(level)
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
if enable_http_handler:
|
|
19
|
+
_http_loguru_handler(level)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def _stdout_loguru_handler(level: LogLevelType) -> None:
|
port_ocean/middlewares.py
CHANGED
|
@@ -1,70 +1,73 @@
|
|
|
1
|
+
from typing import Callable, Awaitable
|
|
2
|
+
|
|
3
|
+
from fastapi import Request, Response
|
|
1
4
|
from loguru import logger
|
|
2
|
-
from starlette.requests import Request
|
|
3
|
-
from starlette.responses import Response
|
|
4
5
|
|
|
5
6
|
from port_ocean.exceptions.api import BaseAPIException, InternalServerException
|
|
6
7
|
from .context.event import event_context, EventType
|
|
7
8
|
from .context.ocean import ocean
|
|
8
9
|
from .utils.misc import get_time, generate_uuid
|
|
9
|
-
from starlette.middleware.base import (
|
|
10
|
-
BaseHTTPMiddleware,
|
|
11
|
-
RequestResponseEndpoint,
|
|
12
|
-
)
|
|
13
10
|
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
async def _handle_silently(
|
|
13
|
+
call_next: Callable[[Request], Awaitable[Response]], request: Request
|
|
14
|
+
) -> Response:
|
|
15
|
+
response: Response
|
|
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)
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
except BaseAPIException as ex:
|
|
25
|
+
response = ex.response()
|
|
26
|
+
if response.status_code < 500:
|
|
27
|
+
logger.bind(exception=str(ex)).info(
|
|
28
|
+
"Request did not succeed due to client-side error"
|
|
25
29
|
)
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
else:
|
|
31
|
+
logger.opt(exception=True).warning(
|
|
32
|
+
"Request did not succeed due to server-side error"
|
|
28
33
|
)
|
|
29
|
-
response = await self._handle_silently(request, call_next)
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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")
|
|
35
|
+
except Exception:
|
|
36
|
+
logger.opt(exception=True).error("Request failed due to unexpected error")
|
|
37
|
+
response = InternalServerException().response()
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
return response
|
|
40
40
|
|
|
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)
|
|
54
41
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
42
|
+
async def request_handler(
|
|
43
|
+
request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
44
|
+
) -> Response:
|
|
45
|
+
"""Middleware used by FastAPI to process each request, featuring:
|
|
46
|
+
|
|
47
|
+
- Contextualize request logs with a unique Request ID (UUID4) for each unique request.
|
|
48
|
+
- Catch exceptions during the request handling. Translate custom API exceptions into responses,
|
|
49
|
+
or treat (and log) unexpected exceptions.
|
|
50
|
+
"""
|
|
51
|
+
start_time = get_time(seconds_precision=False)
|
|
52
|
+
request_id = generate_uuid()
|
|
53
|
+
|
|
54
|
+
with logger.contextualize(request_id=request_id):
|
|
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
|
+
)
|
|
63
|
+
response = await _handle_silently(call_next, request)
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
end_time = get_time(seconds_precision=False)
|
|
66
|
+
time_elapsed = round(end_time - start_time, 5)
|
|
67
|
+
response.headers["X-Request-ID"] = request_id
|
|
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")
|
|
69
72
|
|
|
70
73
|
return response
|
port_ocean/ocean.py
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import contextlib
|
|
3
2
|
import sys
|
|
4
3
|
import threading
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
5
|
from typing import Callable, Any, Dict, AsyncIterator, Type
|
|
6
6
|
|
|
7
7
|
from fastapi import FastAPI, APIRouter
|
|
8
8
|
from loguru import logger
|
|
9
9
|
from pydantic import BaseModel
|
|
10
|
-
from starlette.applications import Starlette
|
|
11
|
-
from starlette.middleware import Middleware
|
|
12
|
-
from starlette.responses import JSONResponse
|
|
13
|
-
from starlette.routing import Route, Mount, Router
|
|
14
10
|
from starlette.types import Scope, Receive, Send
|
|
15
11
|
|
|
12
|
+
from port_ocean.core.handlers.resync_state_updater import ResyncStateUpdater
|
|
13
|
+
from port_ocean.core.models import Runtime
|
|
16
14
|
from port_ocean.clients.port.client import PortClient
|
|
17
15
|
from port_ocean.config.settings import (
|
|
18
16
|
IntegrationConfiguration,
|
|
@@ -22,29 +20,27 @@ from port_ocean.context.ocean import (
|
|
|
22
20
|
ocean,
|
|
23
21
|
initialize_port_ocean_context,
|
|
24
22
|
)
|
|
25
|
-
from port_ocean.core.handlers.resync_state_updater import ResyncStateUpdater
|
|
26
23
|
from port_ocean.core.integrations.base import BaseIntegration
|
|
27
|
-
from port_ocean.core.models import Runtime
|
|
28
24
|
from port_ocean.log.sensetive import sensitive_log_filter
|
|
29
|
-
from port_ocean.middlewares import
|
|
25
|
+
from port_ocean.middlewares import request_handler
|
|
30
26
|
from port_ocean.utils.repeat import repeat_every
|
|
31
27
|
from port_ocean.utils.signal import signal_handler
|
|
32
28
|
from port_ocean.version import __integration_version__
|
|
29
|
+
from port_ocean.utils.misc import IntegrationStateStatus
|
|
33
30
|
|
|
34
31
|
|
|
35
32
|
class Ocean:
|
|
36
33
|
def __init__(
|
|
37
34
|
self,
|
|
38
|
-
app:
|
|
35
|
+
app: FastAPI | None = None,
|
|
39
36
|
integration_class: Callable[[PortOceanContext], BaseIntegration] | None = None,
|
|
40
|
-
integration_router:
|
|
37
|
+
integration_router: APIRouter | None = None,
|
|
41
38
|
config_factory: Type[BaseModel] | None = None,
|
|
42
39
|
config_override: Dict[str, Any] | None = None,
|
|
43
40
|
):
|
|
44
41
|
initialize_port_ocean_context(self)
|
|
45
|
-
|
|
46
|
-
self.
|
|
47
|
-
self.integration_router = integration_router or Router()
|
|
42
|
+
self.fast_api_app = app or FastAPI()
|
|
43
|
+
self.fast_api_app.middleware("http")(request_handler)
|
|
48
44
|
|
|
49
45
|
self.config = IntegrationConfiguration(
|
|
50
46
|
# type: ignore
|
|
@@ -56,6 +52,7 @@ class Ocean:
|
|
|
56
52
|
sensitive_log_filter.hide_sensitive_strings(
|
|
57
53
|
*self.config.get_sensitive_fields_data()
|
|
58
54
|
)
|
|
55
|
+
self.integration_router = integration_router or APIRouter()
|
|
59
56
|
|
|
60
57
|
self.port_client = PortClient(
|
|
61
58
|
base_url=self.config.port.base_url,
|
|
@@ -80,20 +77,20 @@ class Ocean:
|
|
|
80
77
|
self,
|
|
81
78
|
) -> None:
|
|
82
79
|
async def execute_resync_all() -> None:
|
|
83
|
-
|
|
80
|
+
await self.resync_state_updater.update_before_resync()
|
|
84
81
|
logger.info("Starting a new scheduled resync")
|
|
85
82
|
try:
|
|
86
83
|
await self.integration.sync_raw_all()
|
|
87
|
-
|
|
84
|
+
await self.resync_state_updater.update_after_resync()
|
|
88
85
|
except asyncio.CancelledError:
|
|
89
86
|
logger.warning(
|
|
90
87
|
"resync was cancelled by the scheduled resync, skipping state update"
|
|
91
88
|
)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
except Exception as e:
|
|
90
|
+
await self.resync_state_updater.update_after_resync(
|
|
91
|
+
IntegrationStateStatus.Failed
|
|
92
|
+
)
|
|
93
|
+
raise e
|
|
97
94
|
|
|
98
95
|
interval = self.config.scheduled_resync_interval
|
|
99
96
|
loop = asyncio.get_event_loop()
|
|
@@ -105,7 +102,7 @@ class Ocean:
|
|
|
105
102
|
repeated_function = repeat_every(
|
|
106
103
|
seconds=interval * 60,
|
|
107
104
|
# Not running the resync immediately because the event listener should run resync on startup
|
|
108
|
-
wait_first=
|
|
105
|
+
wait_first=True,
|
|
109
106
|
)(
|
|
110
107
|
lambda: threading.Thread(
|
|
111
108
|
target=lambda: asyncio.run_coroutine_threadsafe(
|
|
@@ -116,8 +113,10 @@ class Ocean:
|
|
|
116
113
|
await repeated_function()
|
|
117
114
|
|
|
118
115
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
self.fast_api_app.include_router(self.integration_router, prefix="/integration")
|
|
117
|
+
|
|
118
|
+
@asynccontextmanager
|
|
119
|
+
async def lifecycle(_: FastAPI) -> AsyncIterator[None]:
|
|
121
120
|
try:
|
|
122
121
|
await self.integration.start()
|
|
123
122
|
await self._setup_scheduled_resync()
|
|
@@ -128,16 +127,5 @@ class Ocean:
|
|
|
128
127
|
finally:
|
|
129
128
|
signal_handler.exit()
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
self.starlette_app = Starlette(
|
|
135
|
-
routes=[
|
|
136
|
-
Route("/docs", endpoint=health),
|
|
137
|
-
Mount("/integration", routes=self.integration_router.routes),
|
|
138
|
-
],
|
|
139
|
-
middleware=[Middleware(RequestHandlerMiddleware)],
|
|
140
|
-
lifespan=lifespan,
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
await self.starlette_app(scope, receive, send)
|
|
130
|
+
self.fast_api_app.router.lifespan_context = lifecycle
|
|
131
|
+
await self.fast_api_app(scope, receive, send)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from os import path
|
|
2
|
-
from typing import Any, Callable, List, Tuple
|
|
2
|
+
from typing import Any, Callable, Dict, List, Tuple, Union
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
5
|
import pytest_asyncio
|
|
@@ -30,8 +30,8 @@ def port_client_for_fake_integration() -> Tuple[SmokeTestDetails, PortClient]:
|
|
|
30
30
|
def get_mocked_ocean_app(request: Any) -> Callable[[], Ocean]:
|
|
31
31
|
test_dir = path.join(path.dirname(request.module.__file__), "..")
|
|
32
32
|
|
|
33
|
-
def get_ocean_app() -> Ocean:
|
|
34
|
-
return get_integration_ocean_app(test_dir)
|
|
33
|
+
def get_ocean_app(config_overrides: Union[Dict[str, Any], None] = None) -> Ocean:
|
|
34
|
+
return get_integration_ocean_app(test_dir, config_overrides)
|
|
35
35
|
|
|
36
36
|
return get_ocean_app
|
|
37
37
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from inspect import getmembers
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import List, Tuple
|
|
4
|
+
from typing import Any, Dict, List, Tuple, Union
|
|
5
5
|
|
|
6
6
|
from yaml import safe_load
|
|
7
7
|
|
|
@@ -12,7 +12,9 @@ from port_ocean.ocean import Ocean
|
|
|
12
12
|
from port_ocean.utils.misc import get_spec_file, load_module
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def get_integration_ocean_app(
|
|
15
|
+
def get_integration_ocean_app(
|
|
16
|
+
integration_path: str, config_overrides: Union[Dict[str, Any], None] = None
|
|
17
|
+
) -> Ocean:
|
|
16
18
|
spec_file = get_spec_file(Path(integration_path))
|
|
17
19
|
|
|
18
20
|
config_factory = None if not spec_file else spec_file.get("configurations", [])
|
|
@@ -21,9 +23,12 @@ def get_integration_ocean_app(integration_path: str) -> Ocean:
|
|
|
21
23
|
integration_path,
|
|
22
24
|
config_factory,
|
|
23
25
|
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
26
|
+
**(config_overrides or {}),
|
|
27
|
+
**{
|
|
28
|
+
"port": {
|
|
29
|
+
"client_id": "bla",
|
|
30
|
+
"client_secret": "bla",
|
|
31
|
+
},
|
|
27
32
|
},
|
|
28
33
|
},
|
|
29
34
|
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Any, AsyncGenerator
|
|
2
|
+
import asyncio
|
|
3
|
+
from port_ocean.utils.async_iterators import semaphore_async_iterator
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.asyncio
|
|
8
|
+
async def test_semaphore_async_iterator() -> None:
|
|
9
|
+
max_concurrency = 5
|
|
10
|
+
semaphore = asyncio.BoundedSemaphore(max_concurrency)
|
|
11
|
+
|
|
12
|
+
concurrent_tasks = 0
|
|
13
|
+
max_concurrent_tasks = 0
|
|
14
|
+
lock = asyncio.Lock() # Protect shared variables
|
|
15
|
+
|
|
16
|
+
num_tasks = 20
|
|
17
|
+
|
|
18
|
+
async def mock_function() -> AsyncGenerator[str, None]:
|
|
19
|
+
nonlocal concurrent_tasks, max_concurrent_tasks
|
|
20
|
+
|
|
21
|
+
async with lock:
|
|
22
|
+
concurrent_tasks += 1
|
|
23
|
+
if concurrent_tasks > max_concurrent_tasks:
|
|
24
|
+
max_concurrent_tasks = concurrent_tasks
|
|
25
|
+
|
|
26
|
+
await asyncio.sleep(0.1)
|
|
27
|
+
yield "result"
|
|
28
|
+
|
|
29
|
+
async with lock:
|
|
30
|
+
concurrent_tasks -= 1
|
|
31
|
+
|
|
32
|
+
async def consume_iterator(async_iterator: Any) -> None:
|
|
33
|
+
async for _ in async_iterator:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
tasks = [
|
|
37
|
+
consume_iterator(semaphore_async_iterator(semaphore, mock_function))
|
|
38
|
+
for _ in range(num_tasks)
|
|
39
|
+
]
|
|
40
|
+
await asyncio.gather(*tasks)
|
|
41
|
+
|
|
42
|
+
assert (
|
|
43
|
+
max_concurrent_tasks <= max_concurrency
|
|
44
|
+
), f"Max concurrent tasks {max_concurrent_tasks} exceeded semaphore limit {max_concurrency}"
|
|
45
|
+
assert concurrent_tasks == 0, "Not all tasks have completed"
|
|
@@ -2,6 +2,9 @@ import typing
|
|
|
2
2
|
|
|
3
3
|
import aiostream
|
|
4
4
|
|
|
5
|
+
if typing.TYPE_CHECKING:
|
|
6
|
+
from asyncio import Semaphore
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
async def stream_async_iterators_tasks(
|
|
7
10
|
*tasks: typing.AsyncIterable[typing.Any],
|
|
@@ -47,3 +50,60 @@ async def stream_async_iterators_tasks(
|
|
|
47
50
|
async with combine.stream() as streamer:
|
|
48
51
|
async for batch_items in streamer:
|
|
49
52
|
yield batch_items
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def semaphore_async_iterator(
|
|
56
|
+
semaphore: "Semaphore",
|
|
57
|
+
function: typing.Callable[[], typing.AsyncIterator[typing.Any]],
|
|
58
|
+
) -> typing.AsyncIterator[typing.Any]:
|
|
59
|
+
"""
|
|
60
|
+
Executes an asynchronous iterator function under a semaphore to limit concurrency.
|
|
61
|
+
|
|
62
|
+
This function ensures that the provided asynchronous iterator function is executed
|
|
63
|
+
while respecting the concurrency limit imposed by the semaphore. It acquires the
|
|
64
|
+
semaphore before executing the function and releases it after the function completes,
|
|
65
|
+
thus controlling the number of concurrent executions.
|
|
66
|
+
|
|
67
|
+
Parameters:
|
|
68
|
+
semaphore (asyncio.Semaphore | asyncio.BoundedSemaphore): The semaphore used to limit concurrency.
|
|
69
|
+
function (Callable[[], AsyncIterator[Any]]): A nullary asynchronous function, - apply arguments with `functools.partial` or an anonymous function (lambda)
|
|
70
|
+
that returns an asynchronous iterator. This function is executed under the semaphore.
|
|
71
|
+
|
|
72
|
+
Yields:
|
|
73
|
+
Any: The items yielded by the asynchronous iterator function.
|
|
74
|
+
|
|
75
|
+
Usage:
|
|
76
|
+
```python
|
|
77
|
+
import asyncio
|
|
78
|
+
|
|
79
|
+
async def async_iterator_function(param1, param2):
|
|
80
|
+
# Your async code here
|
|
81
|
+
yield ...
|
|
82
|
+
|
|
83
|
+
async def async_generator_function():
|
|
84
|
+
# Your async code to retrieve items
|
|
85
|
+
param1 = "your_param1"
|
|
86
|
+
yield param1
|
|
87
|
+
|
|
88
|
+
async def main():
|
|
89
|
+
semaphore = asyncio.BoundedSemaphore(50)
|
|
90
|
+
param2 = "your_param2"
|
|
91
|
+
|
|
92
|
+
tasks = [
|
|
93
|
+
semaphore_async_iterator(
|
|
94
|
+
semaphore,
|
|
95
|
+
lambda: async_iterator_function(param1, param2) # functools.partial(async_iterator_function, param1, param2)
|
|
96
|
+
)
|
|
97
|
+
async for param1 in async_generator_function()
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
async for batch in stream_async_iterators_tasks(*tasks):
|
|
101
|
+
# Process each batch
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
asyncio.run(main())
|
|
105
|
+
```
|
|
106
|
+
"""
|
|
107
|
+
async with semaphore:
|
|
108
|
+
async for result in function():
|
|
109
|
+
yield result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: port-ocean
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.4
|
|
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
|
|
@@ -16,7 +16,6 @@ Classifier: Programming Language :: Python
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
20
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
22
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -26,7 +25,7 @@ Requires-Dist: aiostream (>=0.5.2,<0.7.0)
|
|
|
26
25
|
Requires-Dist: click (>=8.1.3,<9.0.0) ; extra == "cli"
|
|
27
26
|
Requires-Dist: confluent-kafka (>=2.1.1,<3.0.0)
|
|
28
27
|
Requires-Dist: cookiecutter (>=2.1.1,<3.0.0) ; extra == "cli"
|
|
29
|
-
Requires-Dist: fastapi (>=0.100,<0.
|
|
28
|
+
Requires-Dist: fastapi (>=0.100,<0.112)
|
|
30
29
|
Requires-Dist: httpx (>=0.24.1,<0.28.0)
|
|
31
30
|
Requires-Dist: jinja2-time (>=0.2.0,<0.3.0) ; extra == "cli"
|
|
32
31
|
Requires-Dist: jq (>=1.8.0,<2.0.0)
|
|
@@ -40,7 +39,7 @@ Requires-Dist: rich (>=13.4.1,<14.0.0) ; extra == "cli"
|
|
|
40
39
|
Requires-Dist: six (>=1.16.0,<2.0.0)
|
|
41
40
|
Requires-Dist: tomli (>=2.0.1,<3.0.0)
|
|
42
41
|
Requires-Dist: urllib3 (>=1.26.16,<3.0.0)
|
|
43
|
-
Requires-Dist: uvicorn (>=0.22,<0.
|
|
42
|
+
Requires-Dist: uvicorn (>=0.22,<0.31)
|
|
44
43
|
Requires-Dist: werkzeug (>=2.3.4,<4.0.0)
|
|
45
44
|
Project-URL: Repository, https://github.com/port-labs/Port-Ocean
|
|
46
45
|
Description-Content-Type: text/markdown
|
|
@@ -9,14 +9,14 @@ port_ocean/cli/commands/defaults/dock.py,sha256=pFtHrU_LTvb5Ddrzj09Wxy-jg1Ym10wB
|
|
|
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
|
|
11
11
|
port_ocean/cli/commands/main.py,sha256=gj0lmuLep2XeLNuabB7Wk0UVYPT7_CD_rAw5AoUQWSE,1057
|
|
12
|
-
port_ocean/cli/commands/new.py,sha256=
|
|
12
|
+
port_ocean/cli/commands/new.py,sha256=39_RnEZHoY0pWf59z_oXrLXAyVwzmFY0XcpCJrtZINI,3496
|
|
13
13
|
port_ocean/cli/commands/pull.py,sha256=VvrRjLNlfPuLIf7KzeIcbzzdi98Z0M9wCRpXC3QPxdI,2306
|
|
14
14
|
port_ocean/cli/commands/sail.py,sha256=rY7rEMjfy_KXiWvtL0T72TTLgeQ3HW4SOzKkz9wL9nI,2282
|
|
15
15
|
port_ocean/cli/commands/version.py,sha256=hEuIEIcm6Zkamz41Z9nxeSM_4g3oNlAgWwQyDGboh-E,536
|
|
16
16
|
port_ocean/cli/cookiecutter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
port_ocean/cli/cookiecutter/cookiecutter.json,sha256=N5UrAP2e5JbgEDz_WTQFIZlzSveME6x32sHeA7idjh0,481
|
|
18
18
|
port_ocean/cli/cookiecutter/extensions.py,sha256=eQNjZvy2enDkJpvMbBGil77Xk9-38f862wfnmCjdoBc,446
|
|
19
|
-
port_ocean/cli/cookiecutter/hooks/post_gen_project.py,sha256=
|
|
19
|
+
port_ocean/cli/cookiecutter/hooks/post_gen_project.py,sha256=xZbDPSmfP-ZXNlPaqQDsYLuNfdhFpLX9fIshiAd94Qg,535
|
|
20
20
|
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore,sha256=9Mz_WI7XBpKzlJ7ILb4vlcuzYkh98Ql3bP_5GHN1sRY,1034
|
|
21
21
|
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example,sha256=LnNPRe3RnzjWPL4tNLYEQiMvFEZHSy3ceqwQEapcpwE,92
|
|
22
22
|
port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore,sha256=32p1lDW_g5hyBz486GWfDeR9m7ikFlASVri5a8vmNoo,2698
|
|
@@ -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=2EreWOj-N2H7QUjEt5wGiv5KHP4pTZc70tn_wHcpF4w,4657
|
|
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
|
|
@@ -89,18 +89,18 @@ port_ocean/core/handlers/port_app_config/models.py,sha256=YvYtf_44KD_rN4xK-3xHtd
|
|
|
89
89
|
port_ocean/core/handlers/resync_state_updater/__init__.py,sha256=kG6y-JQGpPfuTHh912L_bctIDCzAK4DN-d00S7rguWU,81
|
|
90
90
|
port_ocean/core/handlers/resync_state_updater/updater.py,sha256=Yg9ET6ZV5B9GW7u6zZA6GlB_71kmvxvYX2FWgQNzMvo,3182
|
|
91
91
|
port_ocean/core/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
|
-
port_ocean/core/integrations/base.py,sha256=
|
|
92
|
+
port_ocean/core/integrations/base.py,sha256=JxqQApEf7phi_Q_b4U0mKNomxIPdDyKPq3AbApFmhjU,3007
|
|
93
93
|
port_ocean/core/integrations/mixins/__init__.py,sha256=FA1FEKMM6P-L2_m7Q4L20mFa4_RgZnwSRmTCreKcBVM,220
|
|
94
94
|
port_ocean/core/integrations/mixins/events.py,sha256=Ddfx2L4FpghV38waF8OfVeOV0bHBxNIgjU-q5ffillI,2341
|
|
95
95
|
port_ocean/core/integrations/mixins/handler.py,sha256=mZ7-0UlG3LcrwJttFbMe-R4xcOU2H_g33tZar7PwTv8,3771
|
|
96
96
|
port_ocean/core/integrations/mixins/sync.py,sha256=B9fEs8faaYLLikH9GBjE_E61vo0bQDjIGQsQ1SRXOlA,3931
|
|
97
|
-
port_ocean/core/integrations/mixins/sync_raw.py,sha256=
|
|
97
|
+
port_ocean/core/integrations/mixins/sync_raw.py,sha256=BGS5EnZ2N3ifcAi94Wo-ZassSJ-_Se9eFJMpBDT7pNY,18841
|
|
98
98
|
port_ocean/core/integrations/mixins/utils.py,sha256=7y1rGETZIjOQadyIjFJXIHKkQFKx_SwiP-TrAIsyyLY,2303
|
|
99
99
|
port_ocean/core/models.py,sha256=dJ2_olTdbjUpObQJNmg7e7EENU_zZiX6XOaknNp54B0,1342
|
|
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=TLmTMqn4uHGaHgZK8PMIJ0TVJlPB4iP7xl9rx7GtCyY,426
|
|
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
|
|
@@ -112,10 +112,10 @@ port_ocean/helpers/async_client.py,sha256=SRlP6o7_FCSY3UHnRlZdezppePVxxOzZ0z861v
|
|
|
112
112
|
port_ocean/helpers/retry.py,sha256=IQ0RfQ2T5o6uoZh2WW2nrFH5TT6K_k3y2Im0HDp5j9Y,15059
|
|
113
113
|
port_ocean/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
114
114
|
port_ocean/log/handlers.py,sha256=k9G_Mb4ga2-Jke9irpdlYqj6EYiwv0gEsh4TgyqqOmI,2853
|
|
115
|
-
port_ocean/log/logger_setup.py,sha256=
|
|
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=9wYCdyzRZGK1vjEJ28FY_DkfwDNENmXp504UKPf5NaQ,2727
|
|
118
|
+
port_ocean/ocean.py,sha256=Oe4H3kKtkj52uNO4Rd_47iY3MBdrTtshXZ_16q7A8bM,5071
|
|
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
|
|
@@ -124,15 +124,16 @@ port_ocean/tests/clients/port/mixins/test_entities.py,sha256=A9myrnkLhKSQrnOLv1Z
|
|
|
124
124
|
port_ocean/tests/conftest.py,sha256=JXASSS0IY0nnR6bxBflhzxS25kf4iNaABmThyZ0mZt8,101
|
|
125
125
|
port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py,sha256=Yv03P-LDcJCKZ21exiTFrcT1eu0zn6Z954dilxrb52Y,10842
|
|
126
126
|
port_ocean/tests/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
127
|
-
port_ocean/tests/helpers/fixtures.py,sha256=
|
|
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=NYDno5RKBcjSVJUmnmHZVmRDcGmFRoyoBxKt2ls699w,1858
|
|
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
|
|
133
|
+
port_ocean/tests/utils/test_async_iterators.py,sha256=3PLk1emEXekb8LcC5GgVh3OicaX15i5WyaJT_eFnu_4,1336
|
|
133
134
|
port_ocean/utils/__init__.py,sha256=KMGnCPXZJbNwtgxtyMycapkDz8tpSyw23MSYT3iVeHs,91
|
|
134
135
|
port_ocean/utils/async_http.py,sha256=arnH458TExn2Dju_Sy6pHas_vF5RMWnOp-jBz5WAAcE,1226
|
|
135
|
-
port_ocean/utils/async_iterators.py,sha256=
|
|
136
|
+
port_ocean/utils/async_iterators.py,sha256=CPXskYWkhkZtAG-ducEwM8537t3z5usPEqXR9vcivzw,3715
|
|
136
137
|
port_ocean/utils/cache.py,sha256=3KItZDE2yVrbVDr-hoM8lNna8s2dlpxhP4ICdLjH4LQ,2231
|
|
137
138
|
port_ocean/utils/misc.py,sha256=0q2cJ5psqxn_5u_56pT7vOVQ3shDM02iC1lzyWQ_zl0,2098
|
|
138
139
|
port_ocean/utils/queue_utils.py,sha256=KWWl8YVnG-glcfIHhM6nefY-2sou_C6DVP1VynQwzB4,2762
|
|
@@ -140,8 +141,8 @@ port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,32
|
|
|
140
141
|
port_ocean/utils/signal.py,sha256=K-6kKFQTltcmKDhtyZAcn0IMa3sUpOHGOAUdWKgx0_E,1369
|
|
141
142
|
port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
|
|
142
143
|
port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
|
|
143
|
-
port_ocean-0.12.
|
|
144
|
-
port_ocean-0.12.
|
|
145
|
-
port_ocean-0.12.
|
|
146
|
-
port_ocean-0.12.
|
|
147
|
-
port_ocean-0.12.
|
|
144
|
+
port_ocean-0.12.4.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
145
|
+
port_ocean-0.12.4.dist-info/METADATA,sha256=sguRwwu52e3UgwQimCYFM3jiqEscIToBDFefaIdiWgs,6614
|
|
146
|
+
port_ocean-0.12.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
147
|
+
port_ocean-0.12.4.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
|
|
148
|
+
port_ocean-0.12.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|