port-ocean 0.12.0__tar.gz → 0.12.2__tar.gz

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.

Files changed (148) hide show
  1. {port_ocean-0.12.0 → port_ocean-0.12.2}/PKG-INFO +1 -1
  2. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +24 -13
  3. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +36 -1
  4. port_ocean-0.12.2/port_ocean/tests/utils/test_async_iterators.py +45 -0
  5. port_ocean-0.12.2/port_ocean/utils/async_iterators.py +109 -0
  6. {port_ocean-0.12.0 → port_ocean-0.12.2}/pyproject.toml +1 -1
  7. port_ocean-0.12.0/port_ocean/utils/async_iterators.py +0 -49
  8. {port_ocean-0.12.0 → port_ocean-0.12.2}/LICENSE.md +0 -0
  9. {port_ocean-0.12.0 → port_ocean-0.12.2}/README.md +0 -0
  10. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/__init__.py +0 -0
  11. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/bootstrap.py +0 -0
  12. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/__init__.py +0 -0
  13. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cli.py +0 -0
  14. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/__init__.py +0 -0
  15. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  16. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/defaults/clean.py +0 -0
  17. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/defaults/dock.py +0 -0
  18. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/defaults/group.py +0 -0
  19. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/list_integrations.py +0 -0
  20. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/main.py +0 -0
  21. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/new.py +0 -0
  22. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/pull.py +0 -0
  23. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/sail.py +0 -0
  24. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/commands/version.py +0 -0
  25. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  26. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  27. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  28. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  29. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -0
  30. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  31. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  32. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  33. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  34. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  35. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  36. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  37. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  38. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -0
  39. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  40. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  41. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  42. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  43. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  44. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  45. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  46. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  47. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  48. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/cli/utils.py +0 -0
  49. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/__init__.py +0 -0
  50. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/__init__.py +0 -0
  51. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/authentication.py +0 -0
  52. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/client.py +0 -0
  53. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/mixins/__init__.py +0 -0
  54. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  55. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/mixins/entities.py +0 -0
  56. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/mixins/integrations.py +0 -0
  57. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/mixins/migrations.py +0 -0
  58. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/retry_transport.py +0 -0
  59. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/types.py +0 -0
  60. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/clients/port/utils.py +0 -0
  61. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/config/__init__.py +0 -0
  62. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/config/base.py +0 -0
  63. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/config/dynamic.py +0 -0
  64. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/config/settings.py +0 -0
  65. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/consumers/__init__.py +0 -0
  66. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/consumers/kafka_consumer.py +0 -0
  67. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/context/__init__.py +0 -0
  68. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/context/event.py +0 -0
  69. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/context/ocean.py +0 -0
  70. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/context/resource.py +0 -0
  71. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/__init__.py +0 -0
  72. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/defaults/__init__.py +0 -0
  73. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/defaults/clean.py +0 -0
  74. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/defaults/common.py +0 -0
  75. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/defaults/initialize.py +0 -0
  76. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/event_listener/__init__.py +0 -0
  77. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/event_listener/base.py +0 -0
  78. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/event_listener/factory.py +0 -0
  79. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/event_listener/http.py +0 -0
  80. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/event_listener/kafka.py +0 -0
  81. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/event_listener/once.py +0 -0
  82. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/event_listener/polling.py +0 -0
  83. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/__init__.py +0 -0
  84. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/base.py +0 -0
  85. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  86. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  87. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  88. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  89. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  90. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  91. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  92. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  93. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  94. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  95. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  96. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  97. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  98. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  99. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/integrations/__init__.py +0 -0
  100. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/integrations/base.py +0 -0
  101. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  102. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/integrations/mixins/events.py +0 -0
  103. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/integrations/mixins/handler.py +0 -0
  104. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/integrations/mixins/sync.py +0 -0
  105. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/integrations/mixins/sync_raw.py +0 -0
  106. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/integrations/mixins/utils.py +0 -0
  107. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/models.py +0 -0
  108. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/ocean_types.py +0 -0
  109. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/core/utils.py +0 -0
  110. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/exceptions/__init__.py +0 -0
  111. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/exceptions/api.py +0 -0
  112. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/exceptions/base.py +0 -0
  113. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/exceptions/clients.py +0 -0
  114. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/exceptions/context.py +0 -0
  115. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/exceptions/core.py +0 -0
  116. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/exceptions/port_defaults.py +0 -0
  117. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/exceptions/utils.py +0 -0
  118. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/helpers/__init__.py +0 -0
  119. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/helpers/async_client.py +0 -0
  120. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/helpers/retry.py +0 -0
  121. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/log/__init__.py +0 -0
  122. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/log/handlers.py +0 -0
  123. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/log/logger_setup.py +0 -0
  124. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/log/sensetive.py +0 -0
  125. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/middlewares.py +0 -0
  126. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/ocean.py +0 -0
  127. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/py.typed +0 -0
  128. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/run.py +0 -0
  129. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/sonar-project.properties +0 -0
  130. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/__init__.py +0 -0
  131. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
  132. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/conftest.py +0 -0
  133. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/helpers/__init__.py +0 -0
  134. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/helpers/fixtures.py +0 -0
  135. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/helpers/integration.py +0 -0
  136. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/helpers/ocean_app.py +0 -0
  137. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/helpers/port_client.py +0 -0
  138. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/helpers/smoke_test.py +0 -0
  139. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/tests/test_smoke.py +0 -0
  140. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/utils/__init__.py +0 -0
  141. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/utils/async_http.py +0 -0
  142. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/utils/cache.py +0 -0
  143. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/utils/misc.py +0 -0
  144. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/utils/queue_utils.py +0 -0
  145. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/utils/repeat.py +0 -0
  146. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/utils/signal.py +0 -0
  147. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/utils/time.py +0 -0
  148. {port_ocean-0.12.0 → port_ocean-0.12.2}/port_ocean/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.12.0
3
+ Version: 0.12.2
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
@@ -47,37 +47,48 @@ class JQEntityProcessor(BaseEntityProcessor):
47
47
  pattern = "def env: {}; {} as $ENV | " + pattern
48
48
  return jq.compile(pattern)
49
49
 
50
+ @staticmethod
51
+ def _stop_iterator_handler(func: Any) -> Any:
52
+ """
53
+ Wrap the function to handle StopIteration exceptions.
54
+ Prevents StopIteration from stopping the thread and skipping further queue processing.
55
+ """
56
+
57
+ def inner() -> Any:
58
+ try:
59
+ return func()
60
+ except StopIteration:
61
+ return None
62
+
63
+ return inner
64
+
50
65
  async def _search(self, data: dict[str, Any], pattern: str) -> Any:
51
66
  try:
52
67
  loop = asyncio.get_event_loop()
53
68
  compiled_pattern = self._compile(pattern)
54
69
  func = compiled_pattern.input_value(data)
55
- return await loop.run_in_executor(None, func.first)
70
+ return await loop.run_in_executor(
71
+ None, self._stop_iterator_handler(func.first)
72
+ )
56
73
  except Exception as exc:
57
74
  logger.debug(
58
- f"Failed to search for pattern {pattern} in data {data}, {exc}"
75
+ f"Search failed for pattern '{pattern}' in data: {data}, Error: {exc}"
59
76
  )
60
77
  return None
61
78
 
62
79
  async def _search_as_bool(self, data: dict[str, Any], pattern: str) -> bool:
63
80
  loop = asyncio.get_event_loop()
64
- start_time = loop.time()
81
+
65
82
  compiled_pattern = self._compile(pattern)
66
83
  func = compiled_pattern.input_value(data)
67
- compile_time = loop.time() - start_time
68
- value = await loop.run_in_executor(None, func.first)
69
- execute_time = loop.time() - start_time - compile_time
70
- logger.debug(
71
- f"Search for pattern {execute_time:.2f} seconds, compile time {compile_time:.2f} seconds",
72
- pattern=pattern,
73
- compile_time=compile_time,
74
- execute_time=execute_time,
84
+
85
+ value = await loop.run_in_executor(
86
+ None, self._stop_iterator_handler(func.first)
75
87
  )
76
88
  if isinstance(value, bool):
77
89
  return value
78
-
79
90
  raise EntityProcessorException(
80
- f"Expected boolean value, got {type(value)} instead"
91
+ f"Expected boolean value, got value:{value} of type: {type(value)} instead"
81
92
  )
82
93
 
83
94
  async def _search_as_object(
@@ -195,10 +195,45 @@ class TestJQEntityProcessor:
195
195
  pattern = ".foo"
196
196
  with pytest.raises(
197
197
  EntityProcessorException,
198
- match="Expected boolean value, got <class 'str'> instead",
198
+ match="Expected boolean value, got value:bar of type: <class 'str'> instead",
199
199
  ):
200
200
  await mocked_processor._search_as_bool(data, pattern)
201
201
 
202
+ @pytest.mark.parametrize(
203
+ "pattern, expected",
204
+ [
205
+ ('.parameters[] | select(.name == "not_exists") | .value', None),
206
+ (
207
+ '.parameters[] | select(.name == "parameter_name") | .value',
208
+ "parameter_value",
209
+ ),
210
+ (
211
+ '.parameters[] | select(.name == "another_parameter") | .value',
212
+ "another_value",
213
+ ),
214
+ ],
215
+ )
216
+ async def test_search_fails_on_stop_iteration(
217
+ self, mocked_processor: JQEntityProcessor, pattern: str, expected: Any
218
+ ) -> None:
219
+ data = {
220
+ "parameters": [
221
+ {"name": "parameter_name", "value": "parameter_value"},
222
+ {"name": "another_parameter", "value": "another_value"},
223
+ {"name": "another_parameter", "value": "another_value2"},
224
+ ]
225
+ }
226
+ result = await mocked_processor._search(data, pattern)
227
+ assert result == expected
228
+
229
+ async def test_return_a_list_of_values(
230
+ self, mocked_processor: JQEntityProcessor
231
+ ) -> None:
232
+ data = {"parameters": ["parameter_value", "another_value", "another_value2"]}
233
+ pattern = ".parameters"
234
+ result = await mocked_processor._search(data, pattern)
235
+ assert result == ["parameter_value", "another_value", "another_value2"]
236
+
202
237
  @pytest.mark.timeout(3)
203
238
  async def test_search_performance_10000(
204
239
  self, mocked_processor: JQEntityProcessor
@@ -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"
@@ -0,0 +1,109 @@
1
+ import typing
2
+
3
+ import aiostream
4
+
5
+ if typing.TYPE_CHECKING:
6
+ from asyncio import Semaphore
7
+
8
+
9
+ async def stream_async_iterators_tasks(
10
+ *tasks: typing.AsyncIterable[typing.Any],
11
+ ) -> typing.AsyncIterable[typing.Any]:
12
+ """
13
+ This function takes a list of async iterators and streams the results of each iterator as they are available.
14
+ By using this function you can combine multiple async iterators into a single stream of results, instead of waiting
15
+ for each iterator to finish before starting the next one.
16
+
17
+ Usage:
18
+ ```python
19
+ async def async_iterator1():
20
+ for i in range(10):
21
+ yield i
22
+ await asyncio.sleep(1)
23
+
24
+ async def async_iterator2():
25
+ for i in range(10, 20):
26
+ yield i
27
+ await asyncio.sleep(1)
28
+
29
+ async def main():
30
+ async for result in stream_async_iterators_tasks([async_iterator1(), async_iterator2()]):
31
+ print(result)
32
+ ```
33
+
34
+ Caution - Before using this function, make sure that the third-party API you are calling allows the number of
35
+ concurrent requests you are making. If the API has a rate limit, you may need to adjust the number of concurrent
36
+ requests to avoid hitting the rate limit.
37
+
38
+ :param tasks: A list of async iterators
39
+ :return: A stream of results
40
+ """
41
+ if not tasks:
42
+ return
43
+
44
+ if len(tasks) == 1:
45
+ async for batch_items in tasks[0]:
46
+ yield batch_items
47
+ return
48
+
49
+ combine = aiostream.stream.merge(tasks[0], *tasks[1:])
50
+ async with combine.stream() as streamer:
51
+ async for batch_items in streamer:
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
  [tool.poetry]
2
2
  name = "port-ocean"
3
- version = "0.12.0"
3
+ version = "0.12.2"
4
4
  description = "Port Ocean is a CLI tool for managing your Port projects."
5
5
  readme = "README.md"
6
6
  homepage = "https://app.getport.io"
@@ -1,49 +0,0 @@
1
- import typing
2
-
3
- import aiostream
4
-
5
-
6
- async def stream_async_iterators_tasks(
7
- *tasks: typing.AsyncIterable[typing.Any],
8
- ) -> typing.AsyncIterable[typing.Any]:
9
- """
10
- This function takes a list of async iterators and streams the results of each iterator as they are available.
11
- By using this function you can combine multiple async iterators into a single stream of results, instead of waiting
12
- for each iterator to finish before starting the next one.
13
-
14
- Usage:
15
- ```python
16
- async def async_iterator1():
17
- for i in range(10):
18
- yield i
19
- await asyncio.sleep(1)
20
-
21
- async def async_iterator2():
22
- for i in range(10, 20):
23
- yield i
24
- await asyncio.sleep(1)
25
-
26
- async def main():
27
- async for result in stream_async_iterators_tasks([async_iterator1(), async_iterator2()]):
28
- print(result)
29
- ```
30
-
31
- Caution - Before using this function, make sure that the third-party API you are calling allows the number of
32
- concurrent requests you are making. If the API has a rate limit, you may need to adjust the number of concurrent
33
- requests to avoid hitting the rate limit.
34
-
35
- :param tasks: A list of async iterators
36
- :return: A stream of results
37
- """
38
- if not tasks:
39
- return
40
-
41
- if len(tasks) == 1:
42
- async for batch_items in tasks[0]:
43
- yield batch_items
44
- return
45
-
46
- combine = aiostream.stream.merge(tasks[0], *tasks[1:])
47
- async with combine.stream() as streamer:
48
- async for batch_items in streamer:
49
- yield batch_items
File without changes
File without changes