Skip to content

Commit 4c9dafe

Browse files
authored
Merge pull request #63 from tofupilot/alex/app-2850-enable-custom-certificate
feat: add possibility for custom certificate in requests
2 parents d1aa208 + 9982e48 commit 4c9dafe

3 files changed

Lines changed: 106 additions & 13 deletions

File tree

tofupilot/client.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,23 @@
3434

3535

3636
class TofuPilotClient:
37-
"""Wrapper for TofuPilot's API that provides additional support for handling attachments."""
37+
"""Wrapper for TofuPilot's API that provides additional support for handling attachments.
38+
39+
Args:
40+
api_key (Optional[str]): API key for authentication with TofuPilot's API.
41+
If not provided, the TOFUPILOT_API_KEY environment variable will be used.
42+
url (Optional[str]): Base URL for TofuPilot's API.
43+
If not provided, the TOFUPILOT_URL environment variable or the default endpoint will be used.
44+
verify (Optional[str]): Path to a CA bundle file to verify TofuPilot's server certificate.
45+
Useful for connecting to instances with custom/self-signed certificates.
46+
"""
3847

39-
def __init__(self, api_key: Optional[str] = None, url: Optional[str] = None):
48+
def __init__(
49+
self,
50+
api_key: Optional[str] = None,
51+
url: Optional[str] = None,
52+
verify: Optional[str] = None,
53+
):
4054
self._current_version = version("tofupilot")
4155
print_version_banner(self._current_version)
4256
self._logger = setup_logger(logging.INFO)
@@ -52,6 +66,7 @@ def __init__(self, api_key: Optional[str] = None, url: Optional[str] = None):
5266
"Content-Type": "application/json",
5367
"Authorization": f"Bearer {self._api_key}",
5468
}
69+
self._verify = verify
5570
self._max_attachments = CLIENT_MAX_ATTACHMENTS
5671
self._max_file_size = FILE_MAX_SIZE
5772
check_latest_version(self._logger, self._current_version, "tofupilot")
@@ -159,14 +174,20 @@ def create_run( # pylint: disable=too-many-arguments,too-many-locals
159174
json=payload,
160175
headers=self._headers,
161176
timeout=SECONDS_BEFORE_TIMEOUT,
177+
verify=self._verify,
162178
)
163179
response.raise_for_status()
164180
result = handle_response(self._logger, response)
165181

166182
run_id = result.get("id")
167183
if run_id and attachments:
168184
upload_attachments(
169-
self._logger, self._headers, self._url, attachments, run_id
185+
self._logger,
186+
self._headers,
187+
self._url,
188+
attachments,
189+
run_id,
190+
self._verify,
170191
)
171192

172193
return result
@@ -229,6 +250,7 @@ def create_run_from_openhtf_report(self, file_path: str):
229250
initialize_url,
230251
data=json.dumps(payload),
231252
headers=self._headers,
253+
verify=self._verify,
232254
timeout=SECONDS_BEFORE_TIMEOUT,
233255
)
234256

@@ -249,7 +271,13 @@ def create_run_from_openhtf_report(self, file_path: str):
249271
timeout=SECONDS_BEFORE_TIMEOUT,
250272
)
251273

252-
notify_server(self._headers, self._url, upload_id, run_id)
274+
notify_server(
275+
self._headers,
276+
self._url,
277+
upload_id,
278+
run_id,
279+
self._verify,
280+
)
253281

254282
self._logger.success(
255283
"Attachment %s successfully uploaded and linked to run.",
@@ -293,6 +321,7 @@ def get_runs(self, serial_number: str) -> dict:
293321
response = requests.get(
294322
f"{self._url}/runs",
295323
headers=self._headers,
324+
verify=self._verify,
296325
params=params,
297326
timeout=SECONDS_BEFORE_TIMEOUT,
298327
)
@@ -326,6 +355,7 @@ def delete_run(self, run_id: str) -> dict:
326355
response = requests.delete(
327356
f"{self._url}/runs/{run_id}",
328357
headers=self._headers,
358+
verify=self._verify,
329359
timeout=SECONDS_BEFORE_TIMEOUT,
330360
)
331361
response.raise_for_status()
@@ -366,6 +396,7 @@ def update_unit(
366396
f"{self._url}/units/{serial_number}",
367397
json=payload,
368398
headers=self._headers,
399+
verify=self._verify,
369400
timeout=SECONDS_BEFORE_TIMEOUT,
370401
)
371402
response.raise_for_status()
@@ -399,6 +430,7 @@ def delete_unit(self, serial_number: str) -> dict:
399430
response = requests.delete(
400431
f"{self._url}/units/{serial_number}",
401432
headers=self._headers,
433+
verify=self._verify,
402434
timeout=SECONDS_BEFORE_TIMEOUT,
403435
)
404436
response.raise_for_status()
@@ -430,7 +462,9 @@ def upload_and_create_from_openhtf_report(
430462

431463
# Upload report
432464
try:
433-
upload_id = upload_file(self._headers, self._url, file_path)
465+
upload_id = upload_file(
466+
self._headers, self._url, file_path, self._verify
467+
)
434468
except requests.exceptions.HTTPError as http_err:
435469
return handle_http_error(self._logger, http_err)
436470
except requests.RequestException as e:
@@ -451,6 +485,7 @@ def upload_and_create_from_openhtf_report(
451485
f"{self._url}/import",
452486
json=payload,
453487
headers=self._headers,
488+
verify=self._verify,
454489
timeout=SECONDS_BEFORE_TIMEOUT,
455490
)
456491
response.raise_for_status()
@@ -478,6 +513,7 @@ def get_websocket_url(self) -> dict:
478513
response = requests.get(
479514
f"{self._url}/rooms",
480515
headers=self._headers,
516+
verify=self._verify,
481517
timeout=SECONDS_BEFORE_TIMEOUT,
482518
)
483519
response.raise_for_status()

tofupilot/openhtf/upload.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ class upload: # pylint: disable=invalid-name
2424
This function behaves similarly to manually parsing the OpenHTF JSON test report and calling
2525
`TofuPilotClient().create_run()` with the parsed data.
2626
27+
Args:
28+
api_key (Optional[str]): API key for authentication with TofuPilot's API.
29+
allow_nan (Optional[bool]): Whether to allow NaN values in JSON serialization.
30+
url (Optional[str]): Base URL for TofuPilot's API.
31+
client (Optional[TofuPilotClient]): An existing TofuPilot client instance to use.
32+
verify (Optional[str]): Path to a CA bundle file to verify TofuPilot's server certificate.
33+
Useful for connecting to instances with custom/self-signed certificates.
34+
2735
### Usage Example:
2836
2937
```python
@@ -48,12 +56,14 @@ def __init__(
4856
allow_nan: Optional[bool] = False,
4957
url: Optional[str] = None,
5058
client: Optional[TofuPilotClient] = None,
59+
verify: Optional[str] = None,
5160
):
5261
self.allow_nan = allow_nan
53-
self.client = client or TofuPilotClient(api_key=api_key, url=url)
62+
self.client = client or TofuPilotClient(api_key=api_key, url=url, verify=verify)
5463
self._logger = self.client._logger
5564
self._url = self.client._url
5665
self._headers = self.client._headers
66+
self._verify = verify
5767
self._max_attachments = self.client._max_attachments
5868
self._max_file_size = self.client._max_file_size
5969

@@ -133,6 +143,7 @@ def __call__(self, test_record: TestRecord):
133143
initialize_url,
134144
data=json.dumps(payload),
135145
headers=self._headers,
146+
verify=self._verify,
136147
timeout=SECONDS_BEFORE_TIMEOUT,
137148
)
138149

@@ -148,7 +159,13 @@ def __call__(self, test_record: TestRecord):
148159
timeout=SECONDS_BEFORE_TIMEOUT,
149160
)
150161

151-
notify_server(self._headers, self._url, upload_id, run_id)
162+
notify_server(
163+
self._headers,
164+
self._url,
165+
upload_id,
166+
run_id,
167+
self._verify,
168+
)
152169

153170
self._logger.success(
154171
"Attachment %s successfully uploaded and linked to run.",

tofupilot/utils/files.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,19 @@ def upload_file(
4646
headers: dict,
4747
url: str,
4848
file_path: str,
49+
verify: Optional[str] = None,
4950
) -> bool:
50-
"""Initializes an upload and stores file in it"""
51+
"""Initializes an upload and stores file in it
52+
53+
Args:
54+
headers (dict): Request headers including authorization
55+
url (str): Base API URL
56+
file_path (str): Path to the file to upload
57+
verify (Optional[str]): Path to a CA bundle file to verify the server certificate
58+
59+
Returns:
60+
str: The ID of the created upload
61+
"""
5162
# Upload initialization
5263
initialize_url = f"{url}/uploads/initialize"
5364
file_name = os.path.basename(file_path)
@@ -58,6 +69,7 @@ def upload_file(
5869
data=json.dumps(payload),
5970
headers=headers,
6071
timeout=SECONDS_BEFORE_TIMEOUT,
72+
verify=verify,
6173
)
6274

6375
response.raise_for_status()
@@ -78,14 +90,32 @@ def upload_file(
7890
return upload_id
7991

8092

81-
def notify_server(headers: dict, url: str, upload_id: str, run_id: str) -> bool:
82-
"""Tells TP server to sync upload with newly created run"""
93+
def notify_server(
94+
headers: dict,
95+
url: str,
96+
upload_id: str,
97+
run_id: str,
98+
verify: Optional[str] = None,
99+
) -> bool:
100+
"""Tells TP server to sync upload with newly created run
101+
102+
Args:
103+
headers (dict): Request headers including authorization
104+
url (str): Base API URL
105+
upload_id (str): ID of the upload to link
106+
run_id (str): ID of the run to link to
107+
verify (Optional[str]): Path to a CA bundle file to verify the server certificate
108+
109+
Returns:
110+
bool: True if successful
111+
"""
83112
sync_url = f"{url}/uploads/sync"
84113
sync_payload = {"upload_id": upload_id, "run_id": run_id}
85114

86115
response = requests.post(
87116
sync_url,
88117
data=json.dumps(sync_payload),
118+
verify=verify,
89119
headers=headers,
90120
timeout=SECONDS_BEFORE_TIMEOUT,
91121
)
@@ -99,13 +129,23 @@ def upload_attachments(
99129
url: str,
100130
paths: List[Dict[str, Optional[str]]],
101131
run_id: str,
132+
verify: Optional[str] = None,
102133
):
103-
"""Creates one upload per file and stores them into TofuPilot"""
134+
"""Creates one upload per file and stores them into TofuPilot
135+
136+
Args:
137+
logger (Logger): Logger instance
138+
headers (dict): Request headers including authorization
139+
url (str): Base API URL
140+
paths (List[Dict[str, Optional[str]]]): List of file paths to upload
141+
run_id (str): ID of the run to link files to
142+
verify (Optional[str]): Path to a CA bundle file to verify the server certificate
143+
"""
104144
for file_path in paths:
105145
logger.info("Uploading %s...", file_path)
106146

107-
upload_id = upload_file(headers, url, file_path)
108-
notify_server(headers, url, upload_id, run_id)
147+
upload_id = upload_file(headers, url, file_path, verify)
148+
notify_server(headers, url, upload_id, run_id, verify)
109149

110150
logger.success(
111151
f"Attachment {file_path} successfully uploaded and linked to run."

0 commit comments

Comments
 (0)