port-ocean 0.10.12__tar.gz → 0.12.0__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.10.12 → port_ocean-0.12.0}/PKG-INFO +2 -2
  2. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +14 -7
  3. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/integrations/base.py +1 -1
  4. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/log/sensetive.py +1 -1
  5. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/ocean.py +2 -1
  6. port_ocean-0.12.0/port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py +236 -0
  7. port_ocean-0.12.0/port_ocean/tests/helpers/fixtures.py +46 -0
  8. port_ocean-0.12.0/port_ocean/tests/helpers/integration.py +31 -0
  9. port_ocean-0.12.0/port_ocean/tests/helpers/port_client.py +21 -0
  10. port_ocean-0.12.0/port_ocean/tests/helpers/smoke_test.py +82 -0
  11. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/tests/test_smoke.py +4 -1
  12. {port_ocean-0.10.12 → port_ocean-0.12.0}/pyproject.toml +32 -28
  13. port_ocean-0.10.12/port_ocean/tests/helpers/fixtures.py +0 -110
  14. port_ocean-0.10.12/port_ocean/tests/test_sample.py +0 -2
  15. {port_ocean-0.10.12 → port_ocean-0.12.0}/LICENSE.md +0 -0
  16. {port_ocean-0.10.12 → port_ocean-0.12.0}/README.md +0 -0
  17. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/__init__.py +0 -0
  18. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/bootstrap.py +0 -0
  19. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/__init__.py +0 -0
  20. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cli.py +0 -0
  21. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/__init__.py +0 -0
  22. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  23. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/defaults/clean.py +0 -0
  24. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/defaults/dock.py +0 -0
  25. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/defaults/group.py +0 -0
  26. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/list_integrations.py +0 -0
  27. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/main.py +0 -0
  28. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/new.py +0 -0
  29. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/pull.py +0 -0
  30. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/sail.py +0 -0
  31. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/commands/version.py +0 -0
  32. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  33. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/cookiecutter.json +0 -0
  34. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  35. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +0 -0
  36. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -0
  37. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.env.example +0 -0
  38. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  39. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  40. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/blueprints.json +0 -0
  41. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/port-app-config.yml +0 -0
  42. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  43. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  44. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CONTRIBUTING.md +0 -0
  45. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -0
  46. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  47. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  48. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  49. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  50. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  51. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  52. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/sonar-project.properties +0 -0
  53. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  54. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.py +0 -0
  55. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/cli/utils.py +0 -0
  56. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/__init__.py +0 -0
  57. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/__init__.py +0 -0
  58. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/authentication.py +0 -0
  59. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/client.py +0 -0
  60. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/mixins/__init__.py +0 -0
  61. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  62. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/mixins/entities.py +0 -0
  63. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/mixins/integrations.py +0 -0
  64. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/mixins/migrations.py +0 -0
  65. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/retry_transport.py +0 -0
  66. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/types.py +0 -0
  67. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/clients/port/utils.py +0 -0
  68. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/config/__init__.py +0 -0
  69. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/config/base.py +0 -0
  70. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/config/dynamic.py +0 -0
  71. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/config/settings.py +0 -0
  72. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/consumers/__init__.py +0 -0
  73. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/consumers/kafka_consumer.py +0 -0
  74. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/context/__init__.py +0 -0
  75. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/context/event.py +0 -0
  76. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/context/ocean.py +0 -0
  77. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/context/resource.py +0 -0
  78. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/__init__.py +0 -0
  79. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/defaults/__init__.py +0 -0
  80. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/defaults/clean.py +0 -0
  81. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/defaults/common.py +0 -0
  82. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/defaults/initialize.py +0 -0
  83. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/event_listener/__init__.py +0 -0
  84. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/event_listener/base.py +0 -0
  85. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/event_listener/factory.py +0 -0
  86. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/event_listener/http.py +0 -0
  87. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/event_listener/kafka.py +0 -0
  88. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/event_listener/once.py +0 -0
  89. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/event_listener/polling.py +0 -0
  90. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/__init__.py +0 -0
  91. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/base.py +0 -0
  92. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  93. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  94. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  95. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entities_state_applier/port/applier.py +0 -0
  96. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  97. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  98. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  99. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  100. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  101. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  102. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  103. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  104. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/resync_state_updater/__init__.py +0 -0
  105. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/handlers/resync_state_updater/updater.py +0 -0
  106. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/integrations/__init__.py +0 -0
  107. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  108. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/integrations/mixins/events.py +0 -0
  109. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/integrations/mixins/handler.py +0 -0
  110. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/integrations/mixins/sync.py +0 -0
  111. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/integrations/mixins/sync_raw.py +0 -0
  112. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/integrations/mixins/utils.py +0 -0
  113. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/models.py +0 -0
  114. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/ocean_types.py +0 -0
  115. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/core/utils.py +0 -0
  116. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/exceptions/__init__.py +0 -0
  117. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/exceptions/api.py +0 -0
  118. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/exceptions/base.py +0 -0
  119. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/exceptions/clients.py +0 -0
  120. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/exceptions/context.py +0 -0
  121. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/exceptions/core.py +0 -0
  122. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/exceptions/port_defaults.py +0 -0
  123. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/exceptions/utils.py +0 -0
  124. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/helpers/__init__.py +0 -0
  125. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/helpers/async_client.py +0 -0
  126. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/helpers/retry.py +0 -0
  127. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/log/__init__.py +0 -0
  128. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/log/handlers.py +0 -0
  129. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/log/logger_setup.py +0 -0
  130. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/middlewares.py +0 -0
  131. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/py.typed +0 -0
  132. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/run.py +0 -0
  133. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/sonar-project.properties +0 -0
  134. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/tests/__init__.py +0 -0
  135. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/tests/clients/port/mixins/test_entities.py +0 -0
  136. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/tests/conftest.py +0 -0
  137. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/tests/helpers/__init__.py +0 -0
  138. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/tests/helpers/ocean_app.py +0 -0
  139. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/__init__.py +0 -0
  140. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/async_http.py +0 -0
  141. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/async_iterators.py +0 -0
  142. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/cache.py +0 -0
  143. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/misc.py +0 -0
  144. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/queue_utils.py +0 -0
  145. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/repeat.py +0 -0
  146. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/signal.py +0 -0
  147. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/utils/time.py +0 -0
  148. {port_ocean-0.10.12 → port_ocean-0.12.0}/port_ocean/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.10.12
3
+ Version: 0.12.0
4
4
  Summary: Port Ocean is a CLI tool for managing your Port projects.
5
5
  Home-page: https://app.getport.io
6
6
  Keywords: ocean,port-ocean,port
@@ -28,11 +28,11 @@ Requires-Dist: cookiecutter (>=2.1.1,<3.0.0) ; extra == "cli"
28
28
  Requires-Dist: fastapi (>=0.100,<0.112)
29
29
  Requires-Dist: httpx (>=0.24.1,<0.28.0)
30
30
  Requires-Dist: jinja2-time (>=0.2.0,<0.3.0) ; extra == "cli"
31
+ Requires-Dist: jq (>=1.8.0,<2.0.0)
31
32
  Requires-Dist: loguru (>=0.7.0,<0.8.0)
32
33
  Requires-Dist: pydantic[dotenv] (>=1.10.8,<2.0.0)
33
34
  Requires-Dist: pydispatcher (>=2.0.7,<3.0.0)
34
35
  Requires-Dist: pyhumps (>=3.8.0,<4.0.0)
35
- Requires-Dist: pyjq (>=2.6.0,<3.0.0)
36
36
  Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
37
37
  Requires-Dist: pyyaml (>=6.0,<7.0)
38
38
  Requires-Dist: rich (>=13.4.1,<14.0.0) ; extra == "cli"
@@ -1,11 +1,10 @@
1
1
  import asyncio
2
- import functools
3
2
  from asyncio import Task
4
3
  from dataclasses import dataclass, field
5
4
  from functools import lru_cache
6
5
  from typing import Any, Optional
7
6
 
8
- import pyjq as jq # type: ignore
7
+ import jq # type: ignore
9
8
  from loguru import logger
10
9
 
11
10
  from port_ocean.context.ocean import ocean
@@ -52,8 +51,8 @@ class JQEntityProcessor(BaseEntityProcessor):
52
51
  try:
53
52
  loop = asyncio.get_event_loop()
54
53
  compiled_pattern = self._compile(pattern)
55
- first_value_callable = functools.partial(compiled_pattern.first, data)
56
- return await loop.run_in_executor(None, first_value_callable)
54
+ func = compiled_pattern.input_value(data)
55
+ return await loop.run_in_executor(None, func.first)
57
56
  except Exception as exc:
58
57
  logger.debug(
59
58
  f"Failed to search for pattern {pattern} in data {data}, {exc}"
@@ -62,10 +61,18 @@ class JQEntityProcessor(BaseEntityProcessor):
62
61
 
63
62
  async def _search_as_bool(self, data: dict[str, Any], pattern: str) -> bool:
64
63
  loop = asyncio.get_event_loop()
64
+ start_time = loop.time()
65
65
  compiled_pattern = self._compile(pattern)
66
- first_value_callable = functools.partial(compiled_pattern.first, data)
67
- value = await loop.run_in_executor(None, first_value_callable)
68
-
66
+ 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,
75
+ )
69
76
  if isinstance(value, bool):
70
77
  return value
71
78
 
@@ -54,7 +54,7 @@ class BaseIntegration(SyncRawMixin, SyncMixin):
54
54
  """
55
55
  Initializes handlers, establishes integration at the specified port, and starts the event listener.
56
56
  """
57
- logger.info("Starting integration")
57
+ logger.info("Starting integration", integration_type=self.context.config.integration.type)
58
58
  if self.started:
59
59
  raise IntegrationAlreadyStartedException("Integration already started")
60
60
 
@@ -21,7 +21,7 @@ secret_patterns = {
21
21
  "GitHub": r"[g|G][i|I][t|T][h|H][u|U][b|B].*['|\"][0-9a-zA-Z]{35,40}['|\"]",
22
22
  "Google Cloud Platform API Key": r"AIza[0-9A-Za-z\\-_]{35}",
23
23
  "Google Cloud Platform OAuth": r"[0-9]+-[0-9A-Za-z_]{32}\\.apps\\.googleusercontent\\.com",
24
- "Google (GCP) Service-account": r'"type": "service_account"',
24
+ "Google (GCP) Service-account": f'"type":{" "}"service_account"',
25
25
  "Google OAuth Access Token": r"ya29\\.[0-9A-Za-z\\-_]+",
26
26
  "Connection String": r"[a-zA-Z]+:\/\/[^/\s]+:[^/\s]+@[^/\s]+\/[^/\s]+",
27
27
  }
@@ -96,7 +96,8 @@ class Ocean:
96
96
  loop = asyncio.get_event_loop()
97
97
  if interval is not None:
98
98
  logger.info(
99
- f"Setting up scheduled resync, the integration will automatically perform a full resync every {interval} minutes)"
99
+ f"Setting up scheduled resync, the integration will automatically perform a full resync every {interval} minutes)",
100
+ scheduled_interval=interval,
100
101
  )
101
102
  repeated_function = repeat_every(
102
103
  seconds=interval * 60,
@@ -0,0 +1,236 @@
1
+ from typing import Any
2
+ from unittest.mock import AsyncMock, Mock
3
+ import pytest
4
+
5
+ from port_ocean.context.ocean import PortOceanContext
6
+ from port_ocean.core.handlers.entity_processor.jq_entity_processor import (
7
+ JQEntityProcessor,
8
+ )
9
+ from port_ocean.core.ocean_types import CalculationResult
10
+ from port_ocean.exceptions.core import EntityProcessorException
11
+
12
+
13
+ @pytest.mark.asyncio
14
+ class TestJQEntityProcessor:
15
+
16
+ @pytest.fixture
17
+ def mocked_processor(self, monkeypatch: Any) -> JQEntityProcessor:
18
+ mock_context = AsyncMock()
19
+ monkeypatch.setattr(PortOceanContext, "app", mock_context)
20
+ return JQEntityProcessor(mock_context)
21
+
22
+ async def test_compile(self, mocked_processor: JQEntityProcessor) -> None:
23
+ pattern = ".foo"
24
+ compiled = mocked_processor._compile(pattern)
25
+ assert compiled is not None
26
+
27
+ async def test_search(self, mocked_processor: JQEntityProcessor) -> None:
28
+ data = {"foo": "bar"}
29
+ pattern = ".foo"
30
+ result = await mocked_processor._search(data, pattern)
31
+ assert result == "bar"
32
+
33
+ async def test_search_as_bool(self, mocked_processor: JQEntityProcessor) -> None:
34
+ data = {"foo": True}
35
+ pattern = ".foo"
36
+ result = await mocked_processor._search_as_bool(data, pattern)
37
+ assert result is True
38
+
39
+ async def test_search_as_object(self, mocked_processor: JQEntityProcessor) -> None:
40
+ data = {"foo": {"bar": "baz"}}
41
+ obj = {"foo": ".foo.bar"}
42
+ result = await mocked_processor._search_as_object(data, obj)
43
+ assert result == {"foo": "baz"}
44
+
45
+ async def test_get_mapped_entity(self, mocked_processor: JQEntityProcessor) -> None:
46
+ data = {"foo": "bar"}
47
+ raw_entity_mappings = {"foo": ".foo"}
48
+ selector_query = '.foo == "bar"'
49
+ result = await mocked_processor._get_mapped_entity(
50
+ data, raw_entity_mappings, selector_query
51
+ )
52
+ assert result.entity == {"foo": "bar"}
53
+ assert result.did_entity_pass_selector is True
54
+
55
+ async def test_calculate_entity(self, mocked_processor: JQEntityProcessor) -> None:
56
+ data = {"foo": "bar"}
57
+ raw_entity_mappings = {"foo": ".foo"}
58
+ selector_query = '.foo == "bar"'
59
+ result, errors = await mocked_processor._calculate_entity(
60
+ data, raw_entity_mappings, None, selector_query
61
+ )
62
+ assert len(result) == 1
63
+ assert result[0].entity == {"foo": "bar"}
64
+ assert result[0].did_entity_pass_selector is True
65
+ assert not errors
66
+
67
+ async def test_parse_items(self, mocked_processor: JQEntityProcessor) -> None:
68
+ mapping = Mock()
69
+ mapping.port.entity.mappings.dict.return_value = {
70
+ "identifier": ".foo",
71
+ "blueprint": ".foo",
72
+ "properties": {"foo": ".foo"},
73
+ }
74
+ mapping.port.items_to_parse = None
75
+ mapping.selector.query = '.foo == "bar"'
76
+ raw_results = [{"foo": "bar"}]
77
+ result = await mocked_processor._parse_items(mapping, raw_results)
78
+ assert isinstance(result, CalculationResult)
79
+ assert len(result.entity_selector_diff.passed) == 1
80
+ assert result.entity_selector_diff.passed[0].properties.get("foo") == "bar"
81
+ assert not result.errors
82
+
83
+ async def test_in_operator(self, mocked_processor: JQEntityProcessor) -> None:
84
+ data = {
85
+ "key": "GetPort_SelfService",
86
+ "name": "GetPort SelfService",
87
+ "desc": "Test",
88
+ "qualifier": "VW",
89
+ "visibility": "public",
90
+ "selectionMode": "NONE",
91
+ "subViews": [
92
+ {
93
+ "key": "GetPort_SelfService_Second",
94
+ "name": "GetPort SelfService Second",
95
+ "qualifier": "SVW",
96
+ "selectionMode": "NONE",
97
+ "subViews": [
98
+ {
99
+ "key": "GetPort_SelfService_Third",
100
+ "name": "GetPort SelfService Third",
101
+ "qualifier": "SVW",
102
+ "selectionMode": "NONE",
103
+ "subViews": [],
104
+ "referencedBy": [],
105
+ },
106
+ {
107
+ "key": "Port_Test",
108
+ "name": "Port Test",
109
+ "qualifier": "SVW",
110
+ "selectionMode": "NONE",
111
+ "subViews": [],
112
+ "referencedBy": [],
113
+ },
114
+ ],
115
+ "referencedBy": [],
116
+ },
117
+ {
118
+ "key": "Python",
119
+ "name": "Python",
120
+ "qualifier": "SVW",
121
+ "selectionMode": "NONE",
122
+ "subViews": [
123
+ {
124
+ "key": "Time",
125
+ "name": "Time",
126
+ "qualifier": "SVW",
127
+ "selectionMode": "NONE",
128
+ "subViews": [
129
+ {
130
+ "key": "port_*****",
131
+ "name": "port-*****",
132
+ "qualifier": "SVW",
133
+ "selectionMode": "NONE",
134
+ "subViews": [
135
+ {
136
+ "key": "port_*****:REferenced",
137
+ "name": "REferenced",
138
+ "qualifier": "VW",
139
+ "visibility": "public",
140
+ "originalKey": "REferenced",
141
+ }
142
+ ],
143
+ "referencedBy": [],
144
+ }
145
+ ],
146
+ "referencedBy": [],
147
+ }
148
+ ],
149
+ "referencedBy": [],
150
+ },
151
+ {
152
+ "key": "GetPort_SelfService:Authentication_Application",
153
+ "name": "Authentication Application",
154
+ "desc": "For auth services",
155
+ "qualifier": "APP",
156
+ "visibility": "private",
157
+ "selectedBranches": ["main"],
158
+ "originalKey": "Authentication_Application",
159
+ },
160
+ ],
161
+ "referencedBy": [],
162
+ }
163
+ pattern = '.subViews | map(select((.qualifier | IN("VW", "SVW"))) | .key)'
164
+ result = await mocked_processor._search(data, pattern)
165
+ assert result == ["GetPort_SelfService_Second", "Python"]
166
+
167
+ async def test_failure_of_jq_expression(
168
+ self, mocked_processor: JQEntityProcessor
169
+ ) -> None:
170
+ data = {"foo": "bar"}
171
+ pattern = ".foo."
172
+ result = await mocked_processor._search(data, pattern)
173
+ assert result is None
174
+
175
+ async def test_search_as_object_failure(
176
+ self, mocked_processor: JQEntityProcessor
177
+ ) -> None:
178
+ data = {"foo": {"bar": "baz"}}
179
+ obj = {"foo": ".foo.bar."}
180
+ result = await mocked_processor._search_as_object(data, obj)
181
+ assert result == {"foo": None}
182
+
183
+ async def test_double_quotes_in_jq_expression(
184
+ self, mocked_processor: JQEntityProcessor
185
+ ) -> None:
186
+ data = {"foo": "bar"}
187
+ pattern = '"shalom"'
188
+ result = await mocked_processor._search(data, pattern)
189
+ assert result == "shalom"
190
+
191
+ async def test_search_as_bool_failure(
192
+ self, mocked_processor: JQEntityProcessor
193
+ ) -> None:
194
+ data = {"foo": "bar"}
195
+ pattern = ".foo"
196
+ with pytest.raises(
197
+ EntityProcessorException,
198
+ match="Expected boolean value, got <class 'str'> instead",
199
+ ):
200
+ await mocked_processor._search_as_bool(data, pattern)
201
+
202
+ @pytest.mark.timeout(3)
203
+ async def test_search_performance_10000(
204
+ self, mocked_processor: JQEntityProcessor
205
+ ) -> None:
206
+ """
207
+ This test is to check the performance of the search method when called 10000 times.
208
+ """
209
+ data = {"foo": "bar"}
210
+ pattern = ".foo"
211
+ for _ in range(10000):
212
+ result = await mocked_processor._search(data, pattern)
213
+ assert result == "bar"
214
+
215
+ @pytest.mark.timeout(15)
216
+ async def test_parse_items_performance_10000(
217
+ self, mocked_processor: JQEntityProcessor
218
+ ) -> None:
219
+ """
220
+ This test is to check the performance of the parse_items method when called 10000 times.
221
+ """
222
+ mapping = Mock()
223
+ mapping.port.entity.mappings.dict.return_value = {
224
+ "identifier": ".foo",
225
+ "blueprint": ".foo",
226
+ "properties": {"foo": ".foo"},
227
+ }
228
+ mapping.port.items_to_parse = None
229
+ mapping.selector.query = '.foo == "bar"'
230
+ raw_results = [{"foo": "bar"}]
231
+ for _ in range(10000):
232
+ result = await mocked_processor._parse_items(mapping, raw_results)
233
+ assert isinstance(result, CalculationResult)
234
+ assert len(result.entity_selector_diff.passed) == 1
235
+ assert result.entity_selector_diff.passed[0].properties.get("foo") == "bar"
236
+ assert not result.errors
@@ -0,0 +1,46 @@
1
+ from os import path
2
+ from typing import Any, Callable, List, Tuple
3
+
4
+ import pytest
5
+ import pytest_asyncio
6
+
7
+ from port_ocean.clients.port.client import PortClient
8
+ from port_ocean.core.handlers.port_app_config.models import ResourceConfig
9
+ from port_ocean.ocean import Ocean
10
+ from port_ocean.tests.helpers.ocean_app import (
11
+ get_integation_resource_configs,
12
+ get_integration_ocean_app,
13
+ )
14
+ from port_ocean.tests.helpers.smoke_test import (
15
+ SmokeTestDetails,
16
+ get_port_client_for_fake_integration,
17
+ get_smoke_test_details,
18
+ )
19
+
20
+
21
+ @pytest.fixture
22
+ def port_client_for_fake_integration() -> Tuple[SmokeTestDetails, PortClient]:
23
+ smoke_test_details = get_smoke_test_details()
24
+ port_client = get_port_client_for_fake_integration()
25
+
26
+ return smoke_test_details, port_client
27
+
28
+
29
+ @pytest_asyncio.fixture
30
+ def get_mocked_ocean_app(request: Any) -> Callable[[], Ocean]:
31
+ test_dir = path.join(path.dirname(request.module.__file__), "..")
32
+
33
+ def get_ocean_app() -> Ocean:
34
+ return get_integration_ocean_app(test_dir)
35
+
36
+ return get_ocean_app
37
+
38
+
39
+ @pytest_asyncio.fixture
40
+ def get_mock_ocean_resource_configs(request: Any) -> Callable[[], List[ResourceConfig]]:
41
+ module_dir = path.join(path.dirname(request.module.__file__), "..")
42
+
43
+ def get_ocean_resource_configs() -> List[ResourceConfig]:
44
+ return get_integation_resource_configs(module_dir)
45
+
46
+ return get_ocean_resource_configs
@@ -0,0 +1,31 @@
1
+ from typing import List
2
+
3
+ from loguru import logger
4
+
5
+ from port_ocean.clients.port.client import PortClient
6
+
7
+
8
+ async def cleanup_integration(client: PortClient, blueprints: List[str]) -> None:
9
+ for blueprint in blueprints:
10
+ try:
11
+ bp = await client.get_blueprint(blueprint)
12
+ if bp is not None:
13
+ migration_id = await client.delete_blueprint(
14
+ identifier=blueprint, delete_entities=True
15
+ )
16
+ if migration_id:
17
+ await client.wait_for_migration_to_complete(
18
+ migration_id=migration_id
19
+ )
20
+ except Exception as bp_e:
21
+ logger.info(f"Skipping missing blueprint ({blueprint}): {bp_e}")
22
+ headers = await client.auth.headers()
23
+ try:
24
+ await client.client.delete(
25
+ f"{client.auth.api_url}/integrations/{client.integration_identifier}",
26
+ headers=headers,
27
+ )
28
+ except Exception as int_e:
29
+ logger.info(
30
+ f"Failed to delete integration ({client.integration_identifier}): {int_e}"
31
+ )
@@ -0,0 +1,21 @@
1
+ from typing import Union
2
+
3
+ from port_ocean.clients.port.client import PortClient
4
+
5
+
6
+ def get_port_client_for_integration(
7
+ client_id: str,
8
+ client_secret: str,
9
+ integration_identifier: str,
10
+ integration_type: str,
11
+ integration_version: str,
12
+ base_url: Union[str, None],
13
+ ) -> PortClient:
14
+ return PortClient(
15
+ base_url=base_url or "https://api.getport/io",
16
+ client_id=client_id,
17
+ client_secret=client_secret,
18
+ integration_identifier=integration_identifier,
19
+ integration_type=integration_type,
20
+ integration_version=integration_version,
21
+ )
@@ -0,0 +1,82 @@
1
+ from os import environ
2
+ from port_ocean.clients.port.client import PortClient
3
+
4
+ from loguru import logger
5
+ from pydantic import BaseModel
6
+
7
+ from port_ocean.tests.helpers.integration import cleanup_integration
8
+ from port_ocean.tests.helpers.port_client import get_port_client_for_integration
9
+
10
+
11
+ class SmokeTestDetails(BaseModel):
12
+ integration_identifier: str
13
+ blueprint_department: str
14
+ blueprint_person: str
15
+ integration_type: str
16
+ integration_version: str
17
+
18
+
19
+ def get_smoke_test_details() -> SmokeTestDetails:
20
+ blueprint_department = "fake-department"
21
+ blueprint_person = "fake-person"
22
+ integration_identifier = "smoke-test-integration"
23
+ smoke_test_suffix = environ.get("SMOKE_TEST_SUFFIX")
24
+ if smoke_test_suffix is not None:
25
+ integration_identifier = f"{integration_identifier}-{smoke_test_suffix}"
26
+ blueprint_person = f"{blueprint_person}-{smoke_test_suffix}"
27
+ blueprint_department = f"{blueprint_department}-{smoke_test_suffix}"
28
+
29
+ return SmokeTestDetails(
30
+ integration_identifier=integration_identifier,
31
+ blueprint_person=blueprint_person,
32
+ blueprint_department=blueprint_department,
33
+ integration_version="0.1.4-dev",
34
+ integration_type="smoke-test",
35
+ )
36
+
37
+
38
+ async def cleanup_smoke_test() -> None:
39
+ smoke_test_details = get_smoke_test_details()
40
+ client_id = environ.get("PORT_CLIENT_ID")
41
+ client_secret = environ.get("PORT_CLIENT_SECRET")
42
+
43
+ if not client_secret or not client_id:
44
+ assert False, "Missing port credentials"
45
+
46
+ base_url = environ.get("PORT_BASE_URL")
47
+ client = get_port_client_for_integration(
48
+ client_id,
49
+ client_secret,
50
+ smoke_test_details.integration_identifier,
51
+ smoke_test_details.integration_type,
52
+ smoke_test_details.integration_version,
53
+ base_url,
54
+ )
55
+
56
+ logger.info("Cleaning up fake integration")
57
+ await cleanup_integration(
58
+ client,
59
+ [smoke_test_details.blueprint_department, smoke_test_details.blueprint_person],
60
+ )
61
+ logger.info("Cleaning up fake integration complete")
62
+
63
+
64
+ def get_port_client_for_fake_integration() -> PortClient:
65
+ smoke_test_details = get_smoke_test_details()
66
+ client_id = environ.get("PORT_CLIENT_ID")
67
+ client_secret = environ.get("PORT_CLIENT_SECRET")
68
+
69
+ if not client_secret or not client_id:
70
+ assert False, "Missing port credentials"
71
+
72
+ base_url = environ.get("PORT_BASE_URL")
73
+ client = get_port_client_for_integration(
74
+ client_id,
75
+ client_secret,
76
+ smoke_test_details.integration_identifier,
77
+ smoke_test_details.integration_type,
78
+ smoke_test_details.integration_version,
79
+ base_url,
80
+ )
81
+
82
+ return client
@@ -4,7 +4,10 @@ import pytest
4
4
 
5
5
  from port_ocean.clients.port.client import PortClient
6
6
  from port_ocean.clients.port.types import UserAgentType
7
- from port_ocean.tests.helpers.fixtures import SmokeTestDetails
7
+ from port_ocean.tests.helpers.smoke_test import SmokeTestDetails
8
+
9
+
10
+ pytestmark = pytest.mark.smoke
8
11
 
9
12
 
10
13
  @pytest.mark.skipif(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "port-ocean"
3
- version = "0.10.12"
3
+ version = "0.12.0"
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"
@@ -36,7 +36,7 @@ build-backend = "poetry.core.masonry.api"
36
36
 
37
37
  [tool.poetry.dependencies]
38
38
  python = "^3.11"
39
- pydantic = {version = "^1.10.8", extras = ["dotenv"]}
39
+ pydantic = { version = "^1.10.8", extras = ["dotenv"] }
40
40
  loguru = "^0.7.0"
41
41
  pyyaml = "^6.0"
42
42
  werkzeug = ">=2.3.4,<4.0.0"
@@ -44,7 +44,7 @@ fastapi = ">=0.100,<0.112"
44
44
  uvicorn = ">=0.22,<0.31"
45
45
  confluent-kafka = "^2.1.1"
46
46
  httpx = ">=0.24.1,<0.28.0"
47
- pyjq = "^2.6.0"
47
+ jq = "^1.8.0"
48
48
  urllib3 = ">=1.26.16,<3.0.0"
49
49
  six = "^1.16.0"
50
50
  pyhumps = "^3.8.0"
@@ -78,6 +78,7 @@ types-python-dateutil = "^2.9.0.20240316"
78
78
  types-pyyaml = "^6.0.12.10"
79
79
  types-toml = "^0.10.8.6"
80
80
  yamllint = "^1.32.0"
81
+ pytest-timeout = "^2.3.1"
81
82
 
82
83
  [tool.towncrier]
83
84
  directory = "changelog"
@@ -87,35 +88,35 @@ package_dir = "."
87
88
  package = "port_ocean"
88
89
  underlines = [""]
89
90
 
90
- [[tool.towncrier.type]]
91
- directory = "breaking"
92
- name = "Breaking Changes"
93
- showcontent = true
91
+ [[tool.towncrier.type]]
92
+ directory = "breaking"
93
+ name = "Breaking Changes"
94
+ showcontent = true
94
95
 
95
- [[tool.towncrier.type]]
96
- directory = "deprecation"
97
- name = "Deprecations"
98
- showcontent = true
96
+ [[tool.towncrier.type]]
97
+ directory = "deprecation"
98
+ name = "Deprecations"
99
+ showcontent = true
99
100
 
100
- [[tool.towncrier.type]]
101
- directory = "feature"
102
- name = "Features"
103
- showcontent = true
101
+ [[tool.towncrier.type]]
102
+ directory = "feature"
103
+ name = "Features"
104
+ showcontent = true
104
105
 
105
- [[tool.towncrier.type]]
106
- directory = "improvement"
107
- name = "Improvements"
108
- showcontent = true
106
+ [[tool.towncrier.type]]
107
+ directory = "improvement"
108
+ name = "Improvements"
109
+ showcontent = true
109
110
 
110
- [[tool.towncrier.type]]
111
- directory = "bugfix"
112
- name = "Bug Fixes"
113
- showcontent = true
111
+ [[tool.towncrier.type]]
112
+ directory = "bugfix"
113
+ name = "Bug Fixes"
114
+ showcontent = true
114
115
 
115
- [[tool.towncrier.type]]
116
- directory = "doc"
117
- name = "Improved Documentation"
118
- showcontent = true
116
+ [[tool.towncrier.type]]
117
+ directory = "doc"
118
+ name = "Improved Documentation"
119
+ showcontent = true
119
120
 
120
121
  [tool.mypy]
121
122
  exclude = [
@@ -174,4 +175,7 @@ exclude = '''
174
175
  [tool.pytest.ini_options]
175
176
  asyncio_mode = "auto"
176
177
  asyncio_default_fixture_loop_scope = "function"
177
- addopts = "-vv -n auto --ignore-glob='./integrations/*' ./port_ocean/tests"
178
+ addopts = "-vv -n auto --durations=10 --color=yes --ignore-glob='./integrations/*' ./port_ocean/tests"
179
+ markers = [
180
+ "smoke: Smoke tests (deselect with '-m \"not smoke\"')"
181
+ ]