Skip to content

Commit 22a7939

Browse files
abschmsambhavtachylatus
authored
fix: broken URLs on Windows due to os.path.join() (#587)
When trying to submit a workflow with Hera v5 on Windows, the use of os.path.join breaks the URLs because it joins with backslashes on Windows. Using urllib.parse.urljoin instead makes it compatible with both Linux and Windows. The error when trying to create a workflow on Windows is "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)" because Hera assumes a JSON response, but gets HTML because of the bad URL. No tests because you test on Linux. --------- Signed-off-by: Alina Borrisholt Schmidt <[email protected]> Signed-off-by: Helge Willum Thingvad <[email protected]> Co-authored-by: Sambhav Kothari <[email protected]> Co-authored-by: Helge Willum Thingvad <[email protected]>
1 parent 48a57a5 commit 22a7939

File tree

3 files changed

+79
-79
lines changed

3 files changed

+79
-79
lines changed

scripts/service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def __str__(self) -> str:
126126
# url
127127
path_params = [p for p in self.params if p.in_ == "path"]
128128
if len(path_params) == 0:
129-
req_url = f"os.path.join(self.host, '{self.url}')"
129+
req_url = f"urljoin(self.host, '{self.url}')"
130130
else:
131131
# note that here we have a condition on `namespace` because `namespace` can be a global configuration. So,
132132
# we either take it from the endpoint (prioritized just in case users rely on the service to use
@@ -137,7 +137,7 @@ def __str__(self) -> str:
137137
req_url_params.append("namespace=namespace if namespace is not None else self.namespace")
138138
else:
139139
req_url_params.append(f"{p.field}={p.name}")
140-
req_url = f"os.path.join(self.host, '{self.url}').format({', '.join(req_url_params)})"
140+
req_url = f"urljoin(self.host, '{self.url}').format({', '.join(req_url_params)})"
141141

142142
# query params
143143
query_params = [p for p in self.params if p.in_ == "query"]
@@ -400,8 +400,8 @@ def get_endpoints(
400400
def get_service_def() -> str:
401401
"""Assembles the service definition string/code representation"""
402402
return """
403+
from urllib.parse import urljoin
403404
import requests
404-
import os
405405
from hera.{module}.models import {imports}
406406
from hera.shared import global_config
407407
from typing import Optional, cast

src/hera/events/service.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import os
21
from typing import Optional, cast
2+
from urllib.parse import urljoin
33

44
import requests
55

@@ -55,7 +55,7 @@ def list_event_sources(
5555
continue_: Optional[str] = None,
5656
) -> EventSourceList:
5757
resp = requests.get(
58-
url=os.path.join(self.host, "api/v1/event-sources/{namespace}").format(
58+
url=urljoin(self.host, "api/v1/event-sources/{namespace}").format(
5959
namespace=namespace if namespace is not None else self.namespace
6060
),
6161
params={
@@ -81,7 +81,7 @@ def list_event_sources(
8181

8282
def create_event_source(self, req: CreateEventSourceRequest, namespace: Optional[str] = None) -> EventSource:
8383
resp = requests.post(
84-
url=os.path.join(self.host, "api/v1/event-sources/{namespace}").format(
84+
url=urljoin(self.host, "api/v1/event-sources/{namespace}").format(
8585
namespace=namespace if namespace is not None else self.namespace
8686
),
8787
params=None,
@@ -99,7 +99,7 @@ def create_event_source(self, req: CreateEventSourceRequest, namespace: Optional
9999

100100
def get_event_source(self, name: str, namespace: Optional[str] = None) -> EventSource:
101101
resp = requests.get(
102-
url=os.path.join(self.host, "api/v1/event-sources/{namespace}/{name}").format(
102+
url=urljoin(self.host, "api/v1/event-sources/{namespace}/{name}").format(
103103
name=name, namespace=namespace if namespace is not None else self.namespace
104104
),
105105
params=None,
@@ -117,7 +117,7 @@ def update_event_source(
117117
self, name: str, req: UpdateEventSourceRequest, namespace: Optional[str] = None
118118
) -> EventSource:
119119
resp = requests.put(
120-
url=os.path.join(self.host, "api/v1/event-sources/{namespace}/{name}").format(
120+
url=urljoin(self.host, "api/v1/event-sources/{namespace}/{name}").format(
121121
name=name, namespace=namespace if namespace is not None else self.namespace
122122
),
123123
params=None,
@@ -145,7 +145,7 @@ def delete_event_source(
145145
dry_run: Optional[list] = None,
146146
) -> EventSourceDeletedResponse:
147147
resp = requests.delete(
148-
url=os.path.join(self.host, "api/v1/event-sources/{namespace}/{name}").format(
148+
url=urljoin(self.host, "api/v1/event-sources/{namespace}/{name}").format(
149149
name=name, namespace=namespace if namespace is not None else self.namespace
150150
),
151151
params={
@@ -168,7 +168,7 @@ def delete_event_source(
168168

169169
def receive_event(self, discriminator: str, req: Item, namespace: Optional[str] = None) -> EventResponse:
170170
resp = requests.post(
171-
url=os.path.join(self.host, "api/v1/events/{namespace}/{discriminator}").format(
171+
url=urljoin(self.host, "api/v1/events/{namespace}/{discriminator}").format(
172172
discriminator=discriminator, namespace=namespace if namespace is not None else self.namespace
173173
),
174174
params=None,
@@ -186,7 +186,7 @@ def receive_event(self, discriminator: str, req: Item, namespace: Optional[str]
186186

187187
def get_info(self) -> InfoResponse:
188188
resp = requests.get(
189-
url=os.path.join(self.host, "api/v1/info"),
189+
url=urljoin(self.host, "api/v1/info"),
190190
params=None,
191191
headers={"Authorization": f"Bearer {self.token}"},
192192
data=None,
@@ -212,7 +212,7 @@ def list_sensors(
212212
continue_: Optional[str] = None,
213213
) -> SensorList:
214214
resp = requests.get(
215-
url=os.path.join(self.host, "api/v1/sensors/{namespace}").format(
215+
url=urljoin(self.host, "api/v1/sensors/{namespace}").format(
216216
namespace=namespace if namespace is not None else self.namespace
217217
),
218218
params={
@@ -238,7 +238,7 @@ def list_sensors(
238238

239239
def create_sensor(self, req: CreateSensorRequest, namespace: Optional[str] = None) -> Sensor:
240240
resp = requests.post(
241-
url=os.path.join(self.host, "api/v1/sensors/{namespace}").format(
241+
url=urljoin(self.host, "api/v1/sensors/{namespace}").format(
242242
namespace=namespace if namespace is not None else self.namespace
243243
),
244244
params=None,
@@ -256,7 +256,7 @@ def create_sensor(self, req: CreateSensorRequest, namespace: Optional[str] = Non
256256

257257
def get_sensor(self, name: str, namespace: Optional[str] = None, resource_version: Optional[str] = None) -> Sensor:
258258
resp = requests.get(
259-
url=os.path.join(self.host, "api/v1/sensors/{namespace}/{name}").format(
259+
url=urljoin(self.host, "api/v1/sensors/{namespace}/{name}").format(
260260
name=name, namespace=namespace if namespace is not None else self.namespace
261261
),
262262
params={"getOptions.resourceVersion": resource_version},
@@ -272,7 +272,7 @@ def get_sensor(self, name: str, namespace: Optional[str] = None, resource_versio
272272

273273
def update_sensor(self, name: str, req: UpdateSensorRequest, namespace: Optional[str] = None) -> Sensor:
274274
resp = requests.put(
275-
url=os.path.join(self.host, "api/v1/sensors/{namespace}/{name}").format(
275+
url=urljoin(self.host, "api/v1/sensors/{namespace}/{name}").format(
276276
name=name, namespace=namespace if namespace is not None else self.namespace
277277
),
278278
params=None,
@@ -300,7 +300,7 @@ def delete_sensor(
300300
dry_run: Optional[list] = None,
301301
) -> DeleteSensorResponse:
302302
resp = requests.delete(
303-
url=os.path.join(self.host, "api/v1/sensors/{namespace}/{name}").format(
303+
url=urljoin(self.host, "api/v1/sensors/{namespace}/{name}").format(
304304
name=name, namespace=namespace if namespace is not None else self.namespace
305305
),
306306
params={
@@ -335,7 +335,7 @@ def watch_event_sources(
335335
continue_: Optional[str] = None,
336336
) -> EventSourceWatchEvent:
337337
resp = requests.get(
338-
url=os.path.join(self.host, "api/v1/stream/event-sources/{namespace}").format(
338+
url=urljoin(self.host, "api/v1/stream/event-sources/{namespace}").format(
339339
namespace=namespace if namespace is not None else self.namespace
340340
),
341341
params={
@@ -378,7 +378,7 @@ def event_sources_logs(
378378
insecure_skip_tls_verify_backend: Optional[bool] = None,
379379
) -> EventsourceLogEntry:
380380
resp = requests.get(
381-
url=os.path.join(self.host, "api/v1/stream/event-sources/{namespace}/logs").format(
381+
url=urljoin(self.host, "api/v1/stream/event-sources/{namespace}/logs").format(
382382
namespace=namespace if namespace is not None else self.namespace
383383
),
384384
params={
@@ -421,7 +421,7 @@ def watch_events(
421421
continue_: Optional[str] = None,
422422
) -> Event:
423423
resp = requests.get(
424-
url=os.path.join(self.host, "api/v1/stream/events/{namespace}").format(
424+
url=urljoin(self.host, "api/v1/stream/events/{namespace}").format(
425425
namespace=namespace if namespace is not None else self.namespace
426426
),
427427
params={
@@ -459,7 +459,7 @@ def watch_sensors(
459459
continue_: Optional[str] = None,
460460
) -> SensorWatchEvent:
461461
resp = requests.get(
462-
url=os.path.join(self.host, "api/v1/stream/sensors/{namespace}").format(
462+
url=urljoin(self.host, "api/v1/stream/sensors/{namespace}").format(
463463
namespace=namespace if namespace is not None else self.namespace
464464
),
465465
params={
@@ -501,7 +501,7 @@ def sensors_logs(
501501
insecure_skip_tls_verify_backend: Optional[bool] = None,
502502
) -> SensorLogEntry:
503503
resp = requests.get(
504-
url=os.path.join(self.host, "api/v1/stream/sensors/{namespace}/logs").format(
504+
url=urljoin(self.host, "api/v1/stream/sensors/{namespace}/logs").format(
505505
namespace=namespace if namespace is not None else self.namespace
506506
),
507507
params={
@@ -531,7 +531,7 @@ def sensors_logs(
531531

532532
def get_user_info(self) -> GetUserInfoResponse:
533533
resp = requests.get(
534-
url=os.path.join(self.host, "api/v1/userinfo"),
534+
url=urljoin(self.host, "api/v1/userinfo"),
535535
params=None,
536536
headers={"Authorization": f"Bearer {self.token}"},
537537
data=None,
@@ -545,7 +545,7 @@ def get_user_info(self) -> GetUserInfoResponse:
545545

546546
def get_version(self) -> Version:
547547
resp = requests.get(
548-
url=os.path.join(self.host, "api/v1/version"),
548+
url=urljoin(self.host, "api/v1/version"),
549549
params=None,
550550
headers={"Authorization": f"Bearer {self.token}"},
551551
data=None,
@@ -568,7 +568,7 @@ def get_artifact_file(
568568
) -> str:
569569
"""Get an artifact."""
570570
resp = requests.get(
571-
url=os.path.join(
571+
url=urljoin(
572572
self.host,
573573
"artifact-files/{namespace}/{idDiscriminator}/{id}/{nodeId}/{artifactDiscriminator}/{artifactName}",
574574
).format(
@@ -593,7 +593,7 @@ def get_artifact_file(
593593
def get_output_artifact_by_uid(self, uid: str, node_id: str, artifact_name: str) -> str:
594594
"""Get an output artifact by UID."""
595595
resp = requests.get(
596-
url=os.path.join(self.host, "artifacts-by-uid/{uid}/{nodeId}/{artifactName}").format(
596+
url=urljoin(self.host, "artifacts-by-uid/{uid}/{nodeId}/{artifactName}").format(
597597
uid=uid, nodeId=node_id, artifactName=artifact_name
598598
),
599599
params=None,
@@ -610,7 +610,7 @@ def get_output_artifact_by_uid(self, uid: str, node_id: str, artifact_name: str)
610610
def get_output_artifact(self, name: str, node_id: str, artifact_name: str, namespace: Optional[str] = None) -> str:
611611
"""Get an output artifact."""
612612
resp = requests.get(
613-
url=os.path.join(self.host, "artifacts/{namespace}/{name}/{nodeId}/{artifactName}").format(
613+
url=urljoin(self.host, "artifacts/{namespace}/{name}/{nodeId}/{artifactName}").format(
614614
name=name,
615615
nodeId=node_id,
616616
artifactName=artifact_name,
@@ -630,7 +630,7 @@ def get_output_artifact(self, name: str, node_id: str, artifact_name: str, names
630630
def get_input_artifact_by_uid(self, uid: str, node_id: str, artifact_name: str) -> str:
631631
"""Get an input artifact by UID."""
632632
resp = requests.get(
633-
url=os.path.join(self.host, "input-artifacts-by-uid/{uid}/{nodeId}/{artifactName}").format(
633+
url=urljoin(self.host, "input-artifacts-by-uid/{uid}/{nodeId}/{artifactName}").format(
634634
uid=uid, nodeId=node_id, artifactName=artifact_name
635635
),
636636
params=None,
@@ -647,7 +647,7 @@ def get_input_artifact_by_uid(self, uid: str, node_id: str, artifact_name: str)
647647
def get_input_artifact(self, name: str, node_id: str, artifact_name: str, namespace: Optional[str] = None) -> str:
648648
"""Get an input artifact."""
649649
resp = requests.get(
650-
url=os.path.join(self.host, "input-artifacts/{namespace}/{name}/{nodeId}/{artifactName}").format(
650+
url=urljoin(self.host, "input-artifacts/{namespace}/{name}/{nodeId}/{artifactName}").format(
651651
name=name,
652652
nodeId=node_id,
653653
artifactName=artifact_name,

0 commit comments

Comments
 (0)