port-ocean 0.14.5__py3-none-any.whl → 0.14.7__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.

@@ -1,3 +1,4 @@
1
+ import re
1
2
  from typing import Any
2
3
 
3
4
  import httpx
@@ -17,7 +18,7 @@ class TokenResponse(BaseModel):
17
18
 
18
19
  @property
19
20
  def expired(self) -> bool:
20
- return self._retrieved_time + self.expires_in < get_time()
21
+ return self._retrieved_time + self.expires_in <= get_time()
21
22
 
22
23
  @property
23
24
  def full_token(self) -> str:
@@ -46,7 +47,11 @@ class PortAuthentication:
46
47
 
47
48
  async def _get_token(self, client_id: str, client_secret: str) -> TokenResponse:
48
49
  logger.info(f"Fetching access token for clientId: {client_id}")
49
-
50
+ if self._is_personal_token(client_id):
51
+ logger.warning(
52
+ "Integration is using personal credentials, make sure to use machine credentials. "
53
+ "Usage of personal credentials might impose unexpected integration behavior."
54
+ )
50
55
  credentials = {"clientId": client_id, "clientSecret": client_secret}
51
56
  response = await self.client.post(
52
57
  f"{self.api_url}/auth/access_token",
@@ -82,3 +87,8 @@ class PortAuthentication:
82
87
  self.client_id, self.client_secret
83
88
  )
84
89
  return self.last_token_object.full_token
90
+
91
+ @staticmethod
92
+ def _is_personal_token(client_id: str) -> bool:
93
+ email_regex = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
94
+ return re.match(email_regex, client_id) is not None
@@ -48,6 +48,7 @@ class EntityClientMixin:
48
48
  ).lower(),
49
49
  "validation_only": str(validation_only).lower(),
50
50
  },
51
+ extensions={"retryable": True},
51
52
  )
52
53
 
53
54
  if response.is_error:
@@ -205,7 +206,6 @@ class EntityClientMixin:
205
206
  "include": ["blueprint", "identifier"],
206
207
  },
207
208
  extensions={"retryable": True},
208
- timeout=30,
209
209
  )
210
210
  handle_status_code(response)
211
211
  return [Entity.parse_obj(result) for result in response.json()["entities"]]
@@ -134,7 +134,7 @@ class RetryTransport(httpx.AsyncBaseTransport, httpx.BaseTransport):
134
134
  """
135
135
  try:
136
136
  transport: httpx.BaseTransport = self._wrapped_transport # type: ignore
137
- if request.method in self._retryable_methods:
137
+ if self._is_retryable_method(request):
138
138
  send_method = partial(transport.handle_request)
139
139
  response = self._retry_operation(request, send_method)
140
140
  else:
@@ -0,0 +1,191 @@
1
+ import uvicorn
2
+ import os
3
+ from typing import Dict, Any
4
+ from fastapi import FastAPI, Request
5
+
6
+ SMOKE_TEST_SUFFIX = os.environ.get("SMOKE_TEST_SUFFIX", "smoke")
7
+
8
+ app = FastAPI()
9
+
10
+ FAKE_DEPARTMENT_BLUEPRINT = {
11
+ "identifier": f"fake-department-{SMOKE_TEST_SUFFIX}",
12
+ "title": "Fake Department",
13
+ "icon": "Blueprint",
14
+ "schema": {"properties": {"name": {"type": "string"}, "id": {"type": "string"}}},
15
+ "relations": {},
16
+ }
17
+ FAKE_PERSON_BLUEPRINT = {
18
+ "identifier": f"fake-person-{SMOKE_TEST_SUFFIX}",
19
+ "title": "Fake Person",
20
+ "icon": "Blueprint",
21
+ "schema": {
22
+ "properties": {
23
+ "status": {
24
+ "type": "string",
25
+ "enum": ["WORKING", "NOPE"],
26
+ "enumColors": {"WORKING": "green", "NOPE": "red"},
27
+ "title": "Status",
28
+ },
29
+ "email": {"type": "string", "format": "email", "title": "Email"},
30
+ "age": {"type": "number", "title": "Age"},
31
+ "bio": {"type": "string", "title": "Bio"},
32
+ }
33
+ },
34
+ "relations": {
35
+ "department": {
36
+ "title": "Department",
37
+ "description": "Fake Department",
38
+ "target": f"fake-department-{SMOKE_TEST_SUFFIX}",
39
+ "required": False,
40
+ "many": False,
41
+ }
42
+ },
43
+ }
44
+
45
+
46
+ @app.router.get("/v1/blueprints/{blueprint_id}")
47
+ @app.router.patch("/v1/blueprints/{blueprint_id}")
48
+ async def get_blueprint(blueprint_id: str) -> Dict[str, Any]:
49
+ return {
50
+ "blueprint": (
51
+ FAKE_PERSON_BLUEPRINT
52
+ if blueprint_id.startswith("fake-person")
53
+ else FAKE_DEPARTMENT_BLUEPRINT
54
+ )
55
+ }
56
+
57
+
58
+ @app.router.post("/v1/entities/search")
59
+ async def search_entities() -> Dict[str, Any]:
60
+ return {"ok": True, "entities": []}
61
+
62
+
63
+ @app.router.get("/v1/integration/{integration_id}")
64
+ @app.router.patch("/v1/integration/{integration_id}")
65
+ @app.router.patch("/v1/integration/{integration_id}/resync-state")
66
+ async def get_integration(integration_id: str) -> Dict[str, Any]:
67
+ return {
68
+ "integration": {
69
+ "identifer": integration_id,
70
+ "resyncState": {
71
+ "status": "completed",
72
+ "lastResyncEnd": "2024-11-20T12:01:54.225362+00:00",
73
+ "lastResyncStart": "2024-11-20T12:01:45.483844+00:00",
74
+ "nextResync": None,
75
+ "intervalInMinuets": None,
76
+ "updatedAt": "2024-11-20T12:01:54.355Z",
77
+ },
78
+ "config": {
79
+ "deleteDependentEntities": True,
80
+ "createMissingRelatedEntities": True,
81
+ "enableMergeEntity": True,
82
+ "resources": [
83
+ {
84
+ "kind": "fake-department",
85
+ "selector": {"query": "true"},
86
+ "port": {
87
+ "entity": {
88
+ "mappings": {
89
+ "identifier": ".id",
90
+ "title": ".name",
91
+ "blueprint": f'"fake-department-{SMOKE_TEST_SUFFIX}"',
92
+ "properties": {"name": ".name", "id": ".id"},
93
+ }
94
+ }
95
+ },
96
+ },
97
+ {
98
+ "kind": "fake-person",
99
+ "selector": {"query": "true"},
100
+ "port": {
101
+ "entity": {
102
+ "mappings": {
103
+ "identifier": ".id",
104
+ "title": ".name",
105
+ "blueprint": f'"fake-person-{SMOKE_TEST_SUFFIX}"',
106
+ "properties": {
107
+ "name": ".name",
108
+ "email": ".email",
109
+ "status": ".status",
110
+ "age": ".age",
111
+ "department": ".department.name",
112
+ },
113
+ "relations": {"department": ".department.id"},
114
+ }
115
+ }
116
+ },
117
+ },
118
+ ],
119
+ },
120
+ "installationType": "OnPrem",
121
+ "_orgId": "org_ZOMGMYUNIQUEID",
122
+ "_id": "integration_0dOOhnlJQDjMPnfe",
123
+ "identifier": f"smoke-test-integration-{SMOKE_TEST_SUFFIX}",
124
+ "integrationType": "smoke-test",
125
+ "createdBy": "APSQAYsYoIwPXqjn6XpwCAgnPakkNO67",
126
+ "updatedBy": "APSQAYsYoIwPXqjn6XpwCAgnPakkNO67",
127
+ "createdAt": "2024-11-20T12:01:42.651Z",
128
+ "updatedAt": "2024-11-20T12:01:54.355Z",
129
+ "clientId": "",
130
+ "logAttributes": {
131
+ "ingestId": "DOHSAIDHOMER",
132
+ "ingestUrl": "http://localhost:5555/logs/integration/DOHSAIDHOMER",
133
+ },
134
+ },
135
+ }
136
+
137
+
138
+ @app.router.post("/v1/blueprints/{blueprint_id}/entities")
139
+ async def upsert_entities(blueprint_id: str, request: Request) -> Dict[str, Any]:
140
+ json = await request.json()
141
+
142
+ return {
143
+ "ok": True,
144
+ "entity": json,
145
+ }
146
+
147
+
148
+ @app.router.post("/v1/auth/access_token")
149
+ async def auth_token() -> Dict[str, Any]:
150
+ return {
151
+ "accessToken": "ZOMG",
152
+ "expiresIn": 1232131231,
153
+ "tokenType": "adadad",
154
+ }
155
+
156
+
157
+ @app.router.delete("/v1/blueprints/{blueprint_id}/all-entities")
158
+ async def delete_blueprint(blueprint_id: str, request: Request) -> Dict[str, Any]:
159
+ return {"migrationId": "ZOMG"}
160
+
161
+
162
+ @app.router.get("/v1/migrations/{migration_id}")
163
+ async def migration(migration_id: str, request: Request) -> Dict[str, Any]:
164
+ return {
165
+ "migration": {
166
+ "id": migration_id,
167
+ "status": "COMPLETE",
168
+ "actor": "Dwayne Scissors Johnson",
169
+ "sourceBlueprint": "leBlue",
170
+ "mapping": {},
171
+ }
172
+ }
173
+
174
+
175
+ CATCH_ALL = "/{full_path:path}"
176
+
177
+
178
+ @app.router.get(CATCH_ALL)
179
+ @app.router.post(CATCH_ALL)
180
+ @app.router.patch(CATCH_ALL)
181
+ @app.router.delete(CATCH_ALL)
182
+ async def catch_all(full_path: str, request: Request) -> str:
183
+ return f"Hello there from fake Port API - {full_path}, thanks for accessing me with {request.method}"
184
+
185
+
186
+ def start() -> None:
187
+ uvicorn.run(app, host="0.0.0.0", port=5555)
188
+
189
+
190
+ if __name__ == "__main__":
191
+ start()
@@ -50,7 +50,7 @@ def test_serialize_record_exc_info_group_exception() -> None:
50
50
 
51
51
  def assert_extra(extra: dict[str, Any]) -> str:
52
52
  exc_info = extra.get("exc_info", None)
53
- assert type(exc_info) is str
53
+ assert isinstance(exc_info, str)
54
54
  return exc_info
55
55
 
56
56
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: port-ocean
3
- Version: 0.14.5
3
+ Version: 0.14.7
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
@@ -45,11 +45,11 @@ port_ocean/cli/cookiecutter/{{cookiecutter.integration_slug}}/tests/test_sample.
45
45
  port_ocean/cli/utils.py,sha256=IUK2UbWqjci-lrcDdynZXqVP5B5TcjF0w5CpEVUks-k,54
46
46
  port_ocean/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  port_ocean/clients/port/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- port_ocean/clients/port/authentication.py,sha256=t3z6h4vld-Tzkpth15sstaMJg0rccX-pXXjNtOa-nCY,2949
48
+ port_ocean/clients/port/authentication.py,sha256=6-uDMWsJ0xLe1-9IoYXHWmwtufj8rJR4BCRXJlSkCSQ,3447
49
49
  port_ocean/clients/port/client.py,sha256=Xd8Jk25Uh4WXY_WW-z1Qbv6F3ZTBFPoOolsxHMfozKw,3366
50
50
  port_ocean/clients/port/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  port_ocean/clients/port/mixins/blueprints.py,sha256=POBl4uDocrgJBw4rvCAzwRcD4jk-uBL6pDAuKMTajdg,4633
52
- port_ocean/clients/port/mixins/entities.py,sha256=WdqT1gyS81pByUl9xIfZz_xEHRaBfDuZ-ekKX53oBSE,8870
52
+ port_ocean/clients/port/mixins/entities.py,sha256=CnSU3dw1RTZUWYBTLP3KP7CyMweNcoDi1OlSWBwjXWU,8894
53
53
  port_ocean/clients/port/mixins/integrations.py,sha256=t8OSa7Iopnpp8IOEcp3a7WgwOcJEBdFow9UbGDKWxKI,4858
54
54
  port_ocean/clients/port/mixins/migrations.py,sha256=A6896oJF6WbFL2WroyTkMzr12yhVyWqGoq9dtLNSKBY,1457
55
55
  port_ocean/clients/port/retry_transport.py,sha256=PtIZOAZ6V-ncpVysRUsPOgt8Sf01QLnTKB5YeKBxkJk,1861
@@ -115,7 +115,7 @@ port_ocean/exceptions/port_defaults.py,sha256=45Bno5JEB-GXztvKsy8mw7TrydQmw13-4J
115
115
  port_ocean/exceptions/utils.py,sha256=gjOqpi-HpY1l4WlMFsGA9yzhxDhajhoGGdDDyGbLnqI,197
116
116
  port_ocean/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
117
117
  port_ocean/helpers/async_client.py,sha256=SRlP6o7_FCSY3UHnRlZdezppePVxxOzZ0z861vE3K40,1783
118
- port_ocean/helpers/retry.py,sha256=WO4yDFUd9NexZ0kESqxeTxBxNabU7_utKzgTj2-kMaM,15632
118
+ port_ocean/helpers/retry.py,sha256=fmvaUFLIW6PICgYH4RI5rrKBXDxCAhR1Gtl0dsb2kMs,15625
119
119
  port_ocean/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
120
  port_ocean/log/handlers.py,sha256=ncVjgqrZRh6BhyRrA6DQG86Wsbxph1yWYuEC0cWfe-Q,3631
121
121
  port_ocean/log/logger_setup.py,sha256=CoEDowe5OwNOL_5clU6Z4faktfh0VWaOTS0VLmyhHjw,2404
@@ -131,12 +131,13 @@ port_ocean/tests/conftest.py,sha256=JXASSS0IY0nnR6bxBflhzxS25kf4iNaABmThyZ0mZt8,
131
131
  port_ocean/tests/core/defaults/test_common.py,sha256=sR7RqB3ZYV6Xn6NIg-c8k5K6JcGsYZ2SCe_PYX5vLYM,5560
132
132
  port_ocean/tests/core/handlers/entity_processor/test_jq_entity_processor.py,sha256=Yv03P-LDcJCKZ21exiTFrcT1eu0zn6Z954dilxrb52Y,10842
133
133
  port_ocean/tests/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
134
+ port_ocean/tests/helpers/fake_port_api.py,sha256=9rtjC6iTQMfzWK6WipkDzzG0b1IIaRmvdJLOyV613vE,6479
134
135
  port_ocean/tests/helpers/fixtures.py,sha256=IQEplbHhRgjrAsZlnXrgSYA5YQEn25I9HgO3_Fjibxg,1481
135
136
  port_ocean/tests/helpers/integration.py,sha256=_RxS-RHpu11lrbhUXYPZp862HLWx8AoD7iZM6iXN8rs,1104
136
137
  port_ocean/tests/helpers/ocean_app.py,sha256=8BysIhNqtTwhjnya5rr0AtrjulfJnJJMFz5cPUxIpLk,2167
137
138
  port_ocean/tests/helpers/port_client.py,sha256=5d6GNr8vNNSOkrz1AdOhxBUKuusr_-UPDP7AVpHasQw,599
138
139
  port_ocean/tests/helpers/smoke_test.py,sha256=_9aJJFRfuGJEg2D2YQJVJRmpreS6gEPHHQq8Q01x4aQ,2697
139
- port_ocean/tests/log/test_handlers.py,sha256=bTOGnuj8fMIEXepwYblRvcg0FKqApCdyCBtAQZ2BlXM,2115
140
+ port_ocean/tests/log/test_handlers.py,sha256=uxgYCEQLP9U5qf-zUN9SgWFogMbYdnBeOVzXZ7E_yFw,2119
140
141
  port_ocean/tests/test_smoke.py,sha256=uix2uIg_yOm8BHDgHw2hTFPy1fiIyxBGW3ENU_KoFlo,2557
141
142
  port_ocean/tests/utils/test_async_iterators.py,sha256=3PLk1emEXekb8LcC5GgVh3OicaX15i5WyaJT_eFnu_4,1336
142
143
  port_ocean/tests/utils/test_cache.py,sha256=GzoS8xGCBDbBcPwSDbdimsMMkRvJATrBC7UmFhdW3fw,4906
@@ -150,8 +151,8 @@ port_ocean/utils/repeat.py,sha256=0EFWM9d8lLXAhZmAyczY20LAnijw6UbIECf5lpGbOas,32
150
151
  port_ocean/utils/signal.py,sha256=K-6kKFQTltcmKDhtyZAcn0IMa3sUpOHGOAUdWKgx0_E,1369
151
152
  port_ocean/utils/time.py,sha256=pufAOH5ZQI7gXvOvJoQXZXZJV-Dqktoj9Qp9eiRwmJ4,1939
152
153
  port_ocean/version.py,sha256=UsuJdvdQlazzKGD3Hd5-U7N69STh8Dq9ggJzQFnu9fU,177
153
- port_ocean-0.14.5.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
154
- port_ocean-0.14.5.dist-info/METADATA,sha256=MW3BLnPGXE2LtquS4-4M46zCiMfpuczwko2Q2of44F0,6673
155
- port_ocean-0.14.5.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
156
- port_ocean-0.14.5.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
157
- port_ocean-0.14.5.dist-info/RECORD,,
154
+ port_ocean-0.14.7.dist-info/LICENSE.md,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
155
+ port_ocean-0.14.7.dist-info/METADATA,sha256=zaTWtHzqHc4IW3chc9bZSQW2b21WAtepfpy2yThDbB8,6673
156
+ port_ocean-0.14.7.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
157
+ port_ocean-0.14.7.dist-info/entry_points.txt,sha256=F_DNUmGZU2Kme-8NsWM5LLE8piGMafYZygRYhOVtcjA,54
158
+ port_ocean-0.14.7.dist-info/RECORD,,