port-ocean 0.4.8__tar.gz → 0.4.10__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 (119) hide show
  1. {port_ocean-0.4.8 → port_ocean-0.4.10}/PKG-INFO +1 -1
  2. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/new.py +11 -1
  3. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/cookiecutter.json +1 -0
  4. port_ocean-0.4.10/port_ocean/cli/cookiecutter/hooks/post_gen_project.py +11 -0
  5. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Makefile +0 -1
  6. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/mixins/entities.py +93 -38
  7. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/retry_transport.py +2 -2
  8. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/utils.py +14 -14
  9. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entities_state_applier/port/applier.py +10 -21
  10. port_ocean-0.4.10/port_ocean/helpers/async_client.py +53 -0
  11. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/utils.py +3 -7
  12. {port_ocean-0.4.8 → port_ocean-0.4.10}/pyproject.toml +1 -1
  13. {port_ocean-0.4.8 → port_ocean-0.4.10}/LICENSE.md +0 -0
  14. {port_ocean-0.4.8 → port_ocean-0.4.10}/README.md +0 -0
  15. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/__init__.py +0 -0
  16. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/bootstrap.py +0 -0
  17. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/__init__.py +0 -0
  18. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cli.py +0 -0
  19. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/__init__.py +0 -0
  20. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/defaults/__init___.py +0 -0
  21. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/defaults/clean.py +0 -0
  22. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/defaults/dock.py +0 -0
  23. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/defaults/group.py +0 -0
  24. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/list_integrations.py +0 -0
  25. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/main.py +0 -0
  26. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/pull.py +0 -0
  27. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/sail.py +0 -0
  28. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/commands/version.py +0 -0
  29. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/__init__.py +0 -0
  30. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/extensions.py +0 -0
  31. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.dockerignore +0 -0
  32. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.gitignore +0 -0
  33. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/resources/.gitignore +0 -0
  34. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/.port/spec.yaml +0 -0
  35. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/CHANGELOG.md +0 -0
  36. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/Dockerfile +0 -0
  37. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/README.md +0 -0
  38. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/changelog/.gitignore +0 -0
  39. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/config.yaml +0 -0
  40. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/debug.py +0 -0
  41. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/main.py +0 -0
  42. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/poetry.toml +0 -0
  43. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/pyproject.toml +0 -0
  44. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/__init__.py +0 -0
  45. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/cli/utils.py +0 -0
  46. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/__init__.py +0 -0
  47. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/__init__.py +0 -0
  48. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/authentication.py +0 -0
  49. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/client.py +0 -0
  50. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/mixins/__init__.py +0 -0
  51. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/mixins/blueprints.py +0 -0
  52. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/mixins/integrations.py +0 -0
  53. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/mixins/migrations.py +0 -0
  54. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/clients/port/types.py +0 -0
  55. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/config/__init__.py +0 -0
  56. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/config/base.py +0 -0
  57. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/config/dynamic.py +0 -0
  58. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/config/settings.py +0 -0
  59. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/consumers/__init__.py +0 -0
  60. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/consumers/base_consumer.py +0 -0
  61. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/consumers/kafka_consumer.py +0 -0
  62. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/context/__init__.py +0 -0
  63. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/context/event.py +0 -0
  64. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/context/ocean.py +0 -0
  65. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/context/resource.py +0 -0
  66. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/context/utils.py +0 -0
  67. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/__init__.py +0 -0
  68. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/defaults/__init__.py +0 -0
  69. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/defaults/clean.py +0 -0
  70. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/defaults/common.py +0 -0
  71. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/defaults/initialize.py +0 -0
  72. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/event_listener/__init__.py +0 -0
  73. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/event_listener/base.py +0 -0
  74. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/event_listener/factory.py +0 -0
  75. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/event_listener/http.py +0 -0
  76. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/event_listener/kafka.py +0 -0
  77. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/event_listener/once.py +0 -0
  78. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/event_listener/polling.py +0 -0
  79. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/__init__.py +0 -0
  80. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/base.py +0 -0
  81. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entities_state_applier/__init__.py +0 -0
  82. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entities_state_applier/base.py +0 -0
  83. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entities_state_applier/port/__init__.py +0 -0
  84. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entities_state_applier/port/get_related_entities.py +0 -0
  85. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entities_state_applier/port/order_by_entities_dependencies.py +0 -0
  86. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entities_state_applier/port/validate_entity_relations.py +0 -0
  87. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entity_processor/__init__.py +0 -0
  88. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entity_processor/base.py +0 -0
  89. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/entity_processor/jq_entity_processor.py +0 -0
  90. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/port_app_config/__init__.py +0 -0
  91. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/port_app_config/api.py +0 -0
  92. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/port_app_config/base.py +0 -0
  93. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/handlers/port_app_config/models.py +0 -0
  94. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/integrations/__init__.py +0 -0
  95. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/integrations/base.py +0 -0
  96. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/integrations/mixins/__init__.py +0 -0
  97. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/integrations/mixins/events.py +0 -0
  98. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/integrations/mixins/handler.py +0 -0
  99. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/integrations/mixins/sync.py +0 -0
  100. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/integrations/mixins/sync_raw.py +0 -0
  101. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/integrations/mixins/utils.py +0 -0
  102. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/models.py +0 -0
  103. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/ocean_types.py +0 -0
  104. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/core/utils.py +0 -0
  105. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/exceptions/__init__.py +0 -0
  106. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/exceptions/api.py +0 -0
  107. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/exceptions/base.py +0 -0
  108. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/exceptions/clients.py +0 -0
  109. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/exceptions/context.py +0 -0
  110. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/exceptions/core.py +0 -0
  111. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/exceptions/port_defaults.py +0 -0
  112. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/helpers/__init__.py +0 -0
  113. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/helpers/retry.py +0 -0
  114. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/logger_setup.py +0 -0
  115. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/middlewares.py +0 -0
  116. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/ocean.py +0 -0
  117. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/py.typed +0 -0
  118. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/run.py +0 -0
  119. {port_ocean-0.4.8 → port_ocean-0.4.10}/port_ocean/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.4.8
3
+ Version: 0.4.10
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
@@ -9,7 +9,14 @@ from port_ocean.cli.utils import cli_root_path
9
9
 
10
10
  @cli_start.command()
11
11
  @click.argument("path", default=".", type=click.Path(exists=True))
12
- def new(path: str) -> None:
12
+ @click.option(
13
+ "--remove-docker-files",
14
+ "remove_docker_files",
15
+ is_flag=True,
16
+ default=False,
17
+ help="Remove Docker files from the generated project. (Used for contributing to Ocean)",
18
+ )
19
+ def new(path: str, remove_docker_files: bool) -> None:
13
20
  """
14
21
  Scaffold a new integration in the given PATH.
15
22
 
@@ -24,6 +31,9 @@ def new(path: str) -> None:
24
31
  result = cookiecutter(
25
32
  f"{cli_root_path}/cookiecutter",
26
33
  output_dir=path,
34
+ extra_context={
35
+ "remove_docker_files": remove_docker_files,
36
+ },
27
37
  )
28
38
  name = result.split("/")[-1]
29
39
 
@@ -5,6 +5,7 @@
5
5
  "full_name": "Your name",
6
6
  "email": "Your address email <you@example.com>",
7
7
  "release_date": "{% now 'local' %}",
8
+ "remove_docker_files": false,
8
9
  "_extensions": [
9
10
  "jinja2_time.TimeExtension",
10
11
  "extensions.VersionExtension"
@@ -0,0 +1,11 @@
1
+ import os
2
+
3
+
4
+ def delete_docker_files():
5
+ if "{{ cookiecutter.public_integration }}" == "True":
6
+ os.remove("Dockerfile")
7
+ os.remove(".dockerignore")
8
+
9
+
10
+ if __name__ == "__main__":
11
+ delete_docker_files()
@@ -4,7 +4,6 @@ define run_checks
4
4
  exit_code=0; \
5
5
  cd $1; \
6
6
  poetry check || exit_code=$$?;\
7
- poetry lock --check || exit_code=$$?;\
8
7
  mypy . || exit_code=$$?; \
9
8
  ruff . || exit_code=$$?; \
10
9
  black --check . || exit_code=$$?; \
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from urllib.parse import quote_plus
2
3
 
3
4
  import httpx
@@ -5,7 +6,10 @@ from loguru import logger
5
6
 
6
7
  from port_ocean.clients.port.authentication import PortAuthentication
7
8
  from port_ocean.clients.port.types import RequestOptions, UserAgentType
8
- from port_ocean.clients.port.utils import handle_status_code
9
+ from port_ocean.clients.port.utils import (
10
+ handle_status_code,
11
+ PORT_HTTP_MAX_CONNECTIONS_LIMIT,
12
+ )
9
13
  from port_ocean.core.models import Entity
10
14
 
11
15
 
@@ -13,6 +17,10 @@ class EntityClientMixin:
13
17
  def __init__(self, auth: PortAuthentication, client: httpx.AsyncClient):
14
18
  self.auth = auth
15
19
  self.client = client
20
+ # Semaphore is used to limit the number of concurrent requests to port, to avoid overloading it.
21
+ # The number of concurrent requests is set to 90% of the max connections limit, to leave some room for other
22
+ # requests that are not related to entities.
23
+ self.semaphore = asyncio.Semaphore(round(0.9 * PORT_HTTP_MAX_CONNECTIONS_LIMIT))
16
24
 
17
25
  async def upsert_entity(
18
26
  self,
@@ -22,24 +30,24 @@ class EntityClientMixin:
22
30
  should_raise: bool = True,
23
31
  ) -> None:
24
32
  validation_only = request_options["validation_only"]
25
- logger.info(
26
- f"{'Validating' if validation_only else 'Upserting'} entity: {entity.identifier} of blueprint: {entity.blueprint}"
27
- )
28
- headers = await self.auth.headers(user_agent_type)
29
-
30
- response = await self.client.post(
31
- f"{self.auth.api_url}/blueprints/{entity.blueprint}/entities",
32
- json=entity.dict(exclude_unset=True, by_alias=True),
33
- headers=headers,
34
- params={
35
- "upsert": "true",
36
- "merge": str(request_options["merge"]).lower(),
37
- "create_missing_related_entities": str(
38
- request_options["create_missing_related_entities"]
39
- ).lower(),
40
- "validation_only": str(validation_only).lower(),
41
- },
42
- )
33
+ async with self.semaphore:
34
+ logger.info(
35
+ f"{'Validating' if validation_only else 'Upserting'} entity: {entity.identifier} of blueprint: {entity.blueprint}"
36
+ )
37
+ headers = await self.auth.headers(user_agent_type)
38
+ response = await self.client.post(
39
+ f"{self.auth.api_url}/blueprints/{entity.blueprint}/entities",
40
+ json=entity.dict(exclude_unset=True, by_alias=True),
41
+ headers=headers,
42
+ params={
43
+ "upsert": "true",
44
+ "merge": str(request_options["merge"]).lower(),
45
+ "create_missing_related_entities": str(
46
+ request_options["create_missing_related_entities"]
47
+ ).lower(),
48
+ "validation_only": str(validation_only).lower(),
49
+ },
50
+ )
43
51
 
44
52
  if response.is_error:
45
53
  logger.error(
@@ -49,34 +57,81 @@ class EntityClientMixin:
49
57
  )
50
58
  handle_status_code(response, should_raise)
51
59
 
52
- async def delete_entity(
60
+ async def batch_upsert_entities(
53
61
  self,
54
- entity: Entity,
62
+ entities: list[Entity],
55
63
  request_options: RequestOptions,
56
64
  user_agent_type: UserAgentType | None = None,
57
65
  should_raise: bool = True,
58
66
  ) -> None:
59
- logger.info(
60
- f"Delete entity: {entity.identifier} of blueprint: {entity.blueprint}"
61
- )
62
- response = await self.client.delete(
63
- f"{self.auth.api_url}/blueprints/{entity.blueprint}/entities/{quote_plus(entity.identifier)}",
64
- headers=await self.auth.headers(user_agent_type),
65
- params={
66
- "delete_dependents": str(
67
- request_options["delete_dependent_entities"]
68
- ).lower()
69
- },
67
+ await asyncio.gather(
68
+ *(
69
+ self.upsert_entity(
70
+ entity,
71
+ request_options,
72
+ user_agent_type,
73
+ should_raise=should_raise,
74
+ )
75
+ for entity in entities
76
+ ),
77
+ return_exceptions=True,
70
78
  )
71
79
 
72
- if response.is_error:
73
- logger.error(
74
- f"Error deleting "
75
- f"entity: {entity.identifier} of "
76
- f"blueprint: {entity.blueprint}"
80
+ async def delete_entity(
81
+ self,
82
+ entity: Entity,
83
+ request_options: RequestOptions,
84
+ user_agent_type: UserAgentType | None = None,
85
+ should_raise: bool = True,
86
+ ) -> None:
87
+ async with self.semaphore:
88
+ logger.info(
89
+ f"Delete entity: {entity.identifier} of blueprint: {entity.blueprint}"
90
+ )
91
+ response = await self.client.delete(
92
+ f"{self.auth.api_url}/blueprints/{entity.blueprint}/entities/{quote_plus(entity.identifier)}",
93
+ headers=await self.auth.headers(user_agent_type),
94
+ params={
95
+ "delete_dependents": str(
96
+ request_options["delete_dependent_entities"]
97
+ ).lower()
98
+ },
77
99
  )
78
100
 
79
- handle_status_code(response, should_raise)
101
+ if response.is_error:
102
+ if response.status_code == 404:
103
+ logger.info(
104
+ f"Weren't able to delete entity: {entity.identifier} of blueprint: {entity.blueprint},"
105
+ f" as it was already deleted from port"
106
+ )
107
+ return
108
+ logger.error(
109
+ f"Error deleting "
110
+ f"entity: {entity.identifier} of "
111
+ f"blueprint: {entity.blueprint}"
112
+ )
113
+
114
+ handle_status_code(response, should_raise)
115
+
116
+ async def batch_delete_entities(
117
+ self,
118
+ entities: list[Entity],
119
+ request_options: RequestOptions,
120
+ user_agent_type: UserAgentType | None = None,
121
+ should_raise: bool = True,
122
+ ) -> None:
123
+ await asyncio.gather(
124
+ *(
125
+ self.delete_entity(
126
+ entity,
127
+ request_options,
128
+ user_agent_type,
129
+ should_raise=should_raise,
130
+ )
131
+ for entity in entities
132
+ ),
133
+ return_exceptions=True,
134
+ )
80
135
 
81
136
  async def validate_entity_exist(self, identifier: str, blueprint: str) -> None:
82
137
  logger.info(f"Validating entity {identifier} of blueprint {blueprint} exists")
@@ -11,8 +11,8 @@ if TYPE_CHECKING:
11
11
 
12
12
 
13
13
  class TokenRetryTransport(RetryTransport):
14
- def __init__(self, port_client: "PortClient", *args: Any, **kwargs: Any) -> None:
15
- super().__init__(*args, **kwargs)
14
+ def __init__(self, port_client: "PortClient", **kwargs: Any) -> None:
15
+ super().__init__(**kwargs)
16
16
  self.port_client = port_client
17
17
 
18
18
  def _is_retryable_method(self, request: httpx.Request) -> bool:
@@ -5,13 +5,11 @@ from loguru import logger
5
5
  from werkzeug.local import LocalStack, LocalProxy
6
6
 
7
7
  from port_ocean.clients.port.retry_transport import TokenRetryTransport
8
+ from port_ocean.helpers.async_client import OceanAsyncClient
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from port_ocean.clients.port.client import PortClient
11
12
 
12
- _http_client: LocalStack[httpx.AsyncClient] = LocalStack()
13
-
14
-
15
13
  # In case the framework sends more requests to port in parallel then allowed by the limits, a PoolTimeout exception will
16
14
  # be raised.
17
15
  # Raising defaults for the timeout, in addition to the limits, will allow request to wait for a connection for a longer
@@ -19,23 +17,25 @@ _http_client: LocalStack[httpx.AsyncClient] = LocalStack()
19
17
  # We don't want to set the max_connections too highly, as it will cause the application to run out of memory.
20
18
  # We also don't want to set the max_keepalive_connections too highly, as it will cause the application to run out of
21
19
  # available connections.
22
- PORT_HTTP_CLIENT_TIMEOUT = httpx.Timeout(10.0)
23
- PORT_HTTP_CLIENT_CONNECTIONS_LIMIT = httpx.Limits(
24
- max_connections=200, max_keepalive_connections=50
25
- )
20
+ PORT_HTTP_MAX_CONNECTIONS_LIMIT = 200
21
+ PORT_HTTP_MAX_KEEP_ALIVE_CONNECTIONS = 50
22
+ PORT_HTTP_TIMEOUT = 10.0
23
+
24
+
25
+ _http_client: LocalStack[httpx.AsyncClient] = LocalStack()
26
26
 
27
27
 
28
28
  def _get_http_client_context(port_client: "PortClient") -> httpx.AsyncClient:
29
29
  client = _http_client.top
30
30
  if client is None:
31
- client = httpx.AsyncClient(
32
- transport=TokenRetryTransport(
33
- port_client,
34
- httpx.AsyncHTTPTransport(),
35
- logger=logger,
31
+ client = OceanAsyncClient(
32
+ TokenRetryTransport,
33
+ transport_kwargs={"port_client": port_client},
34
+ timeout=httpx.Timeout(PORT_HTTP_TIMEOUT),
35
+ limits=httpx.Limits(
36
+ max_connections=PORT_HTTP_MAX_CONNECTIONS_LIMIT,
37
+ max_keepalive_connections=PORT_HTTP_MAX_KEEP_ALIVE_CONNECTIONS,
36
38
  ),
37
- timeout=PORT_HTTP_CLIENT_TIMEOUT,
38
- limits=PORT_HTTP_CLIENT_CONNECTIONS_LIMIT,
39
39
  )
40
40
  _http_client.push(client)
41
41
 
@@ -158,17 +158,11 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
158
158
  ) -> None:
159
159
  logger.info(f"Upserting {len(entities)} entities")
160
160
  if event.port_app_config.create_missing_related_entities:
161
- await asyncio.gather(
162
- *(
163
- self.context.port_client.upsert_entity(
164
- entity,
165
- event.port_app_config.get_port_request_options(),
166
- user_agent_type,
167
- should_raise=False,
168
- )
169
- for entity in entities
170
- ),
171
- return_exceptions=True,
161
+ await self.context.port_client.batch_upsert_entities(
162
+ entities,
163
+ event.port_app_config.get_port_request_options(),
164
+ user_agent_type,
165
+ should_raise=False,
172
166
  )
173
167
  else:
174
168
  ordered_created_entities = reversed(
@@ -188,16 +182,11 @@ class HttpEntitiesStateApplier(BaseEntitiesStateApplier):
188
182
  ) -> None:
189
183
  logger.info(f"Deleting {len(entities)} entities")
190
184
  if event.port_app_config.delete_dependent_entities:
191
- await asyncio.gather(
192
- *(
193
- self.context.port_client.delete_entity(
194
- entity,
195
- event.port_app_config.get_port_request_options(),
196
- user_agent_type,
197
- should_raise=False,
198
- )
199
- for entity in entities
200
- )
185
+ await self.context.port_client.batch_delete_entities(
186
+ entities,
187
+ event.port_app_config.get_port_request_options(),
188
+ user_agent_type,
189
+ should_raise=False,
201
190
  )
202
191
  else:
203
192
  ordered_deleted_entities = order_by_entities_dependencies(entities)
@@ -0,0 +1,53 @@
1
+ from typing import Any, Callable, Type
2
+
3
+ import httpx
4
+ from loguru import logger
5
+
6
+ from port_ocean.helpers.retry import RetryTransport
7
+
8
+
9
+ class OceanAsyncClient(httpx.AsyncClient):
10
+ """
11
+ This class is a wrapper around httpx.AsyncClient that uses a custom transport class.
12
+ This is done to allow passing our custom transport class to the AsyncClient constructor while still allowing
13
+ all the default AsyncClient behavior that is changed when passing a custom transport instance.
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ transport_class: Type[RetryTransport] = RetryTransport,
19
+ transport_kwargs: dict[str, Any] | None = None,
20
+ **kwargs: Any,
21
+ ):
22
+ self._transport_kwargs = transport_kwargs
23
+ self._transport_class = transport_class
24
+ super().__init__(**kwargs)
25
+
26
+ def _init_transport( # type: ignore[override]
27
+ self,
28
+ transport: httpx.AsyncBaseTransport | None = None,
29
+ app: Callable[..., Any] | None = None,
30
+ **kwargs: Any,
31
+ ) -> httpx.AsyncBaseTransport:
32
+ if transport is not None or app is not None:
33
+ return super()._init_transport(transport=transport, app=app, **kwargs)
34
+
35
+ return self._transport_class(
36
+ wrapped_transport=httpx.AsyncHTTPTransport(
37
+ **kwargs,
38
+ ),
39
+ logger=logger,
40
+ **(self._transport_kwargs or {}),
41
+ )
42
+
43
+ def _init_proxy_transport( # type: ignore[override]
44
+ self, proxy: httpx.Proxy, **kwargs: Any
45
+ ) -> httpx.AsyncBaseTransport:
46
+ return self._transport_class(
47
+ wrapped_transport=httpx.AsyncHTTPTransport(
48
+ proxy=proxy,
49
+ **kwargs,
50
+ ),
51
+ logger=logger,
52
+ **(self._transport_kwargs or {}),
53
+ )
@@ -3,6 +3,7 @@ import inspect
3
3
  from asyncio import ensure_future
4
4
  from functools import wraps
5
5
  from importlib.util import module_from_spec, spec_from_file_location
6
+ from pathlib import Path
6
7
  from time import time
7
8
  from traceback import format_exception
8
9
  from types import ModuleType
@@ -13,10 +14,10 @@ import httpx
13
14
  import tomli
14
15
  import yaml
15
16
  from loguru import logger
16
- from pathlib import Path
17
17
  from starlette.concurrency import run_in_threadpool
18
18
  from werkzeug.local import LocalStack, LocalProxy
19
19
 
20
+ from port_ocean.helpers.async_client import OceanAsyncClient
20
21
  from port_ocean.helpers.retry import RetryTransport
21
22
 
22
23
  _http_client: LocalStack[httpx.AsyncClient] = LocalStack()
@@ -25,12 +26,7 @@ _http_client: LocalStack[httpx.AsyncClient] = LocalStack()
25
26
  def _get_http_client_context() -> httpx.AsyncClient:
26
27
  client = _http_client.top
27
28
  if client is None:
28
- client = httpx.AsyncClient(
29
- transport=RetryTransport(
30
- httpx.AsyncHTTPTransport(),
31
- logger=logger,
32
- )
33
- )
29
+ client = OceanAsyncClient(RetryTransport)
34
30
  _http_client.push(client)
35
31
 
36
32
  return client
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "port-ocean"
3
- version = "0.4.8"
3
+ version = "0.4.10"
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"
File without changes
File without changes