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.
- port_ocean/clients/port/authentication.py +12 -2
- port_ocean/clients/port/mixins/entities.py +1 -1
- port_ocean/helpers/retry.py +1 -1
- port_ocean/tests/helpers/fake_port_api.py +191 -0
- port_ocean/tests/log/test_handlers.py +1 -1
- {port_ocean-0.14.5.dist-info → port_ocean-0.14.7.dist-info}/METADATA +1 -1
- {port_ocean-0.14.5.dist-info → port_ocean-0.14.7.dist-info}/RECORD +10 -9
- {port_ocean-0.14.5.dist-info → port_ocean-0.14.7.dist-info}/LICENSE.md +0 -0
- {port_ocean-0.14.5.dist-info → port_ocean-0.14.7.dist-info}/WHEEL +0 -0
- {port_ocean-0.14.5.dist-info → port_ocean-0.14.7.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|
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"]]
|
port_ocean/helpers/retry.py
CHANGED
|
@@ -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
|
|
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()
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
154
|
-
port_ocean-0.14.
|
|
155
|
-
port_ocean-0.14.
|
|
156
|
-
port_ocean-0.14.
|
|
157
|
-
port_ocean-0.14.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|