Skip to content

Commit f538be5

Browse files
authored
Eliminate Remaining requests usage within azure-sdk-tools (Azure#27101)
* eliminate remaining requests usage from azure-sdk-tools * directly add Paul's recommendations, definitely on point * ensure that a None header gets flattened to empty string. * ensure that the urllib3 clients honor the same certificate bundle that requests does
1 parent 9c47777 commit f538be5

File tree

4 files changed

+120
-60
lines changed

4 files changed

+120
-60
lines changed

tools/azure-sdk-tools/devtools_testutils/proxy_startup.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import os
88
import logging
9-
import requests
109
import shlex
1110
import sys
1211
import time
@@ -15,6 +14,8 @@
1514
import pytest
1615
import subprocess
1716

17+
import urllib3
18+
1819
from .config import PROXY_URL
1920
from .helpers import is_live_and_not_recording
2021
from .sanitizers import add_remove_header_sanitizer, set_custom_default_matcher
@@ -33,6 +34,12 @@
3334

3435
discovered_roots = []
3536

37+
from urllib3 import PoolManager, Retry
38+
from urllib3.exceptions import HTTPError
39+
40+
http_client = PoolManager(retries=Retry(total=1, raise_on_status=False))
41+
42+
3643
def get_image_tag(repo_root: str) -> str:
3744
"""Gets the test proxy Docker image tag from the target_version.txt file in /eng/common/testproxy"""
3845
version_file_location = os.path.relpath("eng/common/testproxy/target_version.txt")
@@ -84,10 +91,10 @@ def delete_container() -> None:
8491
def check_availability() -> None:
8592
"""Attempts request to /Info/Available. If a test-proxy instance is responding, we should get a response."""
8693
try:
87-
response = requests.get(PROXY_CHECK_URL, timeout=10)
88-
return response.status_code
94+
response = http_client.request(method="GET", url=PROXY_CHECK_URL, timeout=10)
95+
return response.status
8996
# We get an SSLError if the container is started but the endpoint isn't available yet
90-
except requests.exceptions.SSLError as sslError:
97+
except urllib3.exceptions.SSLError as sslError:
9198
_LOGGER.debug(sslError)
9299
return 404
93100
except Exception as e:
@@ -171,7 +178,7 @@ def start_test_proxy(request) -> None:
171178
proc = subprocess.Popen(
172179
shlex.split('test-proxy start --storage-location="{}" -- --urls "{}"'.format(root, PROXY_URL)),
173180
stdout=log,
174-
stderr=log
181+
stderr=log,
175182
)
176183
os.environ[TOOL_ENV_VAR] = str(proc.pid)
177184
else:

tools/azure-sdk-tools/devtools_testutils/proxy_testcase.py

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
import logging
7-
import requests
87
import six
98
import os
109
from typing import TYPE_CHECKING
@@ -18,9 +17,13 @@
1817

1918
# the trimming function to clean up incoming arguments to the test function we are wrapping
2019
from azure_devtools.scenario_tests.utilities import trim_kwargs_from_test_function
20+
2121
from .config import PROXY_URL
2222
from .helpers import get_test_id, is_live, is_live_and_not_recording, set_recording_id
2323
from .proxy_startup import discovered_roots
24+
from urllib3 import PoolManager, Retry
25+
from urllib3.exceptions import HTTPError
26+
import json
2427

2528
if TYPE_CHECKING:
2629
from typing import Callable, Dict, Tuple
@@ -29,13 +32,22 @@
2932
# To learn about how to migrate SDK tests to the test proxy, please refer to the migration guide at
3033
# https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/test_proxy_migration_guide.md
3134

35+
if os.getenv("REQUESTS_CA_BUNDLE"):
36+
http_client = PoolManager(
37+
retries=Retry(total=3, raise_on_status=False),
38+
cert_reqs="CERT_REQUIRED",
39+
ca_certs=os.getenv("REQUESTS_CA_BUNDLE"),
40+
)
41+
else:
42+
http_client = PoolManager(retries=Retry(total=3, raise_on_status=False))
3243

3344
# defaults
3445
RECORDING_START_URL = "{}/record/start".format(PROXY_URL)
3546
RECORDING_STOP_URL = "{}/record/stop".format(PROXY_URL)
3647
PLAYBACK_START_URL = "{}/playback/start".format(PROXY_URL)
3748
PLAYBACK_STOP_URL = "{}/playback/stop".format(PROXY_URL)
3849

50+
3951
def get_recording_assets(test_id: str) -> str:
4052
"""
4153
Used to retrieve the assets.json given a PYTEST_CURRENT_TEST test id.
@@ -58,45 +70,50 @@ def get_recording_assets(test_id: str) -> str:
5870

5971
return None
6072

73+
6174
def start_record_or_playback(test_id: str) -> "Tuple[str, Dict[str, str]]":
6275
"""Sends a request to begin recording or playing back the provided test.
6376
6477
This returns a tuple, (a, b), where a is the recording ID of the test and b is the `variables` dictionary that maps
6578
test variables to values. If no variable dictionary was stored when the test was recorded, b is an empty dictionary.
6679
"""
6780
variables = {} # this stores a dictionary of test variable values that could have been stored with a recording
68-
81+
6982
json_payload = {"x-recording-file": test_id}
7083
assets_json = get_recording_assets(test_id)
7184
if assets_json:
72-
json_payload["x-recording-assets-file"] = assets_json
85+
json_payload["x-recording-assets-file"] = assets_json
86+
87+
encoded_payload = json.dumps(json_payload).encode("utf-8")
7388

7489
if is_live():
75-
result = requests.post(
76-
RECORDING_START_URL,
77-
json=json_payload,
90+
result = http_client.request(
91+
method="POST",
92+
url=RECORDING_START_URL,
93+
body=encoded_payload,
7894
)
79-
if result.status_code != 200:
80-
message = six.ensure_str(result._content)
95+
if result.status != 200:
96+
message = six.ensure_str(result.data)
8197
raise HttpResponseError(message=message)
8298
recording_id = result.headers["x-recording-id"]
8399

84100
else:
85-
result = requests.post(
86-
PLAYBACK_START_URL,
87-
json=json_payload,
101+
result = http_client.request(
102+
method="POST",
103+
url=PLAYBACK_START_URL,
104+
body=encoded_payload,
88105
)
89-
if result.status_code != 200:
90-
message = six.ensure_str(result._content)
106+
if result.status != 200:
107+
message = six.ensure_str(result.data)
91108
raise HttpResponseError(message=message)
92109

93110
try:
94111
recording_id = result.headers["x-recording-id"]
95112
except KeyError as ex:
96113
six.raise_from(ValueError("No recording file found for {}".format(test_id)), ex)
97-
if result.text:
114+
if result.data:
98115
try:
99-
variables = result.json()
116+
variables = json.loads(result.data.decode("utf-8"))
100117
except ValueError as ex: # would be a JSONDecodeError on Python 3, which subclasses ValueError
101118
six.raise_from(
102119
ValueError("The response body returned from starting playback did not contain valid JSON"),
@@ -109,25 +126,27 @@ def start_record_or_playback(test_id: str) -> "Tuple[str, Dict[str, str]]":
109126

110127

111128
def stop_record_or_playback(test_id: str, recording_id: str, test_variables: "Dict[str, str]") -> None:
112-
if is_live():
113-
response = requests.post(
114-
RECORDING_STOP_URL,
115-
headers={
116-
"x-recording-file": test_id,
117-
"x-recording-id": recording_id,
118-
"x-recording-save": "true",
119-
"Content-Type": "application/json",
120-
},
121-
json=test_variables or {}, # tests don't record successfully unless test_variables is a dictionary
122-
)
123-
else:
124-
response = requests.post(
125-
PLAYBACK_STOP_URL,
126-
headers={"x-recording-id": recording_id},
127-
)
128129
try:
129-
response.raise_for_status()
130-
except requests.HTTPError as e:
130+
if is_live():
131+
http_client.request(
132+
method="POST",
133+
url=RECORDING_STOP_URL,
134+
headers={
135+
"x-recording-file": test_id,
136+
"x-recording-id": recording_id,
137+
"x-recording-save": "true",
138+
"Content-Type": "application/json",
139+
},
140+
# tests don't record successfully unless test_variables is a dictionary
141+
body=json.dumps(test_variables).encode("utf-8") if test_variables else "{}",
142+
)
143+
else:
144+
http_client.request(
145+
method="POST",
146+
url=PLAYBACK_STOP_URL,
147+
headers={"x-recording-id": recording_id},
148+
)
149+
except HTTPError as e:
131150
raise HttpResponseError(
132151
"The test proxy ran into an error while ending the session. Make sure any test variables you record have "
133152
"string values."

tools/azure-sdk-tools/devtools_testutils/sanitizers.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@
44
# license information.
55
# --------------------------------------------------------------------------
66
from typing import TYPE_CHECKING
7-
from urllib.error import HTTPError
8-
import requests
7+
8+
9+
from urllib3 import PoolManager, Retry
10+
import json, os
911

1012
from .config import PROXY_URL
1113
from .helpers import get_recording_id, is_live, is_live_and_not_recording
1214

1315
if TYPE_CHECKING:
1416
from typing import Any, Iterable, Optional
1517

18+
if os.getenv("REQUESTS_CA_BUNDLE"):
19+
http_client = PoolManager(
20+
retries=Retry(total=3, raise_on_status=True),
21+
cert_reqs="CERT_REQUIRED",
22+
ca_certs=os.getenv("REQUESTS_CA_BUNDLE"),
23+
)
24+
else:
25+
http_client = PoolManager(retries=Retry(total=3, raise_on_status=True))
1626

1727
# This file contains methods for adjusting many aspects of test proxy behavior:
1828
#
@@ -552,9 +562,17 @@ def _send_matcher_request(matcher: str, headers: dict, parameters: "Optional[dic
552562
return
553563

554564
headers_to_send = {"x-abstraction-identifier": matcher}
555-
headers_to_send.update(headers)
556-
response = requests.post(f"{PROXY_URL}/Admin/SetMatcher", headers=headers_to_send, json=parameters)
557-
response.raise_for_status()
565+
566+
for key in headers:
567+
if headers[key] is not None:
568+
headers_to_send[key] = headers[key]
569+
570+
http_client.request(
571+
method="POST",
572+
url=f"{PROXY_URL}/Admin/SetMatcher",
573+
headers=headers_to_send,
574+
body=json.dumps(parameters).encode("utf-8"),
575+
)
558576

559577

560578
def _send_recording_options_request(parameters: dict, headers: "Optional[dict]" = None) -> None:
@@ -571,8 +589,17 @@ def _send_recording_options_request(parameters: dict, headers: "Optional[dict]"
571589
if is_live_and_not_recording():
572590
return
573591

574-
response = requests.post(f"{PROXY_URL}/Admin/SetRecordingOptions", headers=headers, json=parameters)
575-
response.raise_for_status()
592+
headers_to_send = {}
593+
for key in headers:
594+
if headers[key] is not None:
595+
headers_to_send[key] = headers[key]
596+
597+
http_client.request(
598+
method="POST",
599+
url=f"{PROXY_URL}/Admin/SetRecordingOptions",
600+
headers=headers_to_send,
601+
body=json.dumps(parameters).encode("utf-8"),
602+
)
576603

577604

578605
def _send_reset_request(headers: dict) -> None:
@@ -587,8 +614,12 @@ def _send_reset_request(headers: dict) -> None:
587614
if is_live_and_not_recording():
588615
return
589616

590-
response = requests.post(f"{PROXY_URL}/Admin/Reset", headers=headers)
591-
response.raise_for_status()
617+
headers_to_send = {}
618+
for key in headers:
619+
if headers[key] is not None:
620+
headers_to_send[key] = headers[key]
621+
622+
request = http_client.request(method="POST", url=f"{PROXY_URL}/Admin/Reset", headers=headers_to_send)
592623

593624

594625
def _send_sanitizer_request(sanitizer: str, parameters: dict) -> None:
@@ -604,15 +635,15 @@ def _send_sanitizer_request(sanitizer: str, parameters: dict) -> None:
604635
if is_live_and_not_recording():
605636
return
606637

607-
response = requests.post(
608-
"{}/Admin/AddSanitizer".format(PROXY_URL),
638+
http_client.request(
639+
method="POST",
640+
url="{}/Admin/AddSanitizer".format(PROXY_URL),
609641
headers={
610642
"x-abstraction-identifier": sanitizer,
611643
"Content-Type": "application/json",
612644
},
613-
json=parameters,
645+
body=json.dumps(parameters).encode("utf-8"),
614646
)
615-
response.raise_for_status()
616647

617648

618649
def _send_transform_request(transform: str, parameters: dict) -> None:
@@ -627,9 +658,9 @@ def _send_transform_request(transform: str, parameters: dict) -> None:
627658
if is_live():
628659
return
629660

630-
response = requests.post(
631-
f"{PROXY_URL}/Admin/AddTransform",
661+
http_client.request(
662+
method="POST",
663+
url=f"{PROXY_URL}/Admin/AddTransform",
632664
headers={"x-abstraction-identifier": transform, "Content-Type": "application/json"},
633-
json=parameters,
665+
body=json.dumps(parameters).encode("utf-8"),
634666
)
635-
response.raise_for_status()

tools/azure-sdk-tools/packaging_tools/swaggertosdk/SwaggerToSdkCore.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
import json
55
import logging
66
import os
7+
from pickle import GET
78
import re
89
import tempfile
910
from pathlib import Path
1011

11-
import requests
12+
from urllib3 import PoolManager, Retry
1213

1314
from github import Github, UnknownObjectException
1415

@@ -26,6 +27,8 @@
2627

2728
DEFAULT_COMMIT_MESSAGE = "Generated from {hexsha}"
2829

30+
http_client = PoolManager(retries=Retry(total=3, raise_on_status=False))
31+
2932

3033
def build_file_content():
3134
autorest_version = autorest_latest_version_finder()
@@ -176,14 +179,14 @@ def read_config_from_github(sdk_id, branch="main", gh_token=None):
176179
_LOGGER.debug("Will try to download: %s", raw_link)
177180
_LOGGER.debug("Token is defined: %s", gh_token is not None)
178181
headers = {"Authorization": "token {}".format(gh_token)} if gh_token else {}
179-
response = requests.get(raw_link, headers=headers)
180-
if response.status_code != 200:
182+
response = http_client.request(method="GET", url=raw_link, headers=headers)
183+
if response.status != 200:
181184
raise ValueError(
182185
"Unable to download conf file for SDK {} branch {}: status code {}".format(
183-
sdk_id, branch, response.status_code
186+
sdk_id, branch, response.status
184187
)
185188
)
186-
return json.loads(response.text)
189+
return json.loads(response.data.decode('utf-8'))
187190

188191

189192
def extract_conf_from_readmes(swagger_files_in_pr, restapi_git_folder, sdk_git_id, config, force_generation=False):

0 commit comments

Comments
 (0)