Skip to content

Commit c904cf1

Browse files
nicornkCopilot
andauthored
bring your own requests.Session() (#98)
* feat: bring your own requests session * use request method instead of post * fix: user-agent * refactor logic from ContextHttpClient into wrapping class to be api compatible to requests.Session * fix test * feat: release v2.1.16 * Update libs/foundry-dev-tools/src/foundry_dev_tools/config/context.py Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent 0506ff4 commit c904cf1

File tree

8 files changed

+61
-27
lines changed

8 files changed

+61
-27
lines changed

docs/changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog],
66
and this project adheres to [Semantic Versioning].
77

8+
## [2.1.16] - 2025-05-16
9+
10+
## Added
11+
- add multipass API to delete Users. (#96)
12+
- bring your own requests.Session() (#98)
13+
14+
## Fixed
15+
- update deploy-pages version to v4
16+
817
## [2.1.15] - 2024-12-18
918

1019
## Added
@@ -333,6 +342,7 @@ and this project adheres to [Semantic Versioning].
333342
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
334343
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
335344

345+
[2.1.16]: https://github.com/emdgroup/foundry-dev-tools/compare/v2.1.15...v2.1.16
336346
[2.1.15]: https://github.com/emdgroup/foundry-dev-tools/compare/v2.1.14...v2.1.15
337347
[2.1.14]: https://github.com/emdgroup/foundry-dev-tools/compare/v2.1.13...v2.1.14
338348
[2.1.13]: https://github.com/emdgroup/foundry-dev-tools/compare/v2.1.12...v2.1.13

docs/configuration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ ctx.config.cache_dir = cache_dir
7878
Configuring Foundry DevTools in Python works through the [FoundryContext].
7979

8080
```python
81+
import requests
8182
from foundry_dev_tools import FoundryContext
8283

8384
# this way it will take the configuration and credentials
@@ -104,6 +105,9 @@ ctx = FoundryContext(config=Config())
104105

105106
# For example to enable some debug logging
106107
ctx = FoundryContext(config=Config(debug=True))
108+
109+
# if you want to bring your own requests.session
110+
ctx = FoundryContext(config=Config(debug=True, requests_session=requests.Session()))
107111
```
108112
### Configuration for Transforms
109113

libs/foundry-dev-tools/src/foundry_dev_tools/clients/api_client.py

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

55
from typing import TYPE_CHECKING, ClassVar, Literal
66

7+
from foundry_dev_tools.errors.handling import ErrorHandlingConfig, raise_foundry_api_error
78
from foundry_dev_tools.utils.clients import build_api_url, build_public_api_url
89

910
if TYPE_CHECKING:
@@ -23,7 +24,6 @@
2324
)
2425

2526
from foundry_dev_tools.config.context import FoundryContext
26-
from foundry_dev_tools.errors.handling import ErrorHandlingConfig
2727

2828

2929
class APIClient:
@@ -93,7 +93,7 @@ def api_request(
9393
else:
9494
headers = {"content-type": "application/json"}
9595

96-
return self.context.client.request(
96+
response = self.context.client.request(
9797
method=method,
9898
url=self.api_url(api_path),
9999
params=params,
@@ -110,8 +110,9 @@ def api_request(
110110
verify=verify,
111111
cert=cert,
112112
json=json,
113-
error_handling=error_handling,
114113
)
114+
raise_foundry_api_error(response, error_handling)
115+
return response
115116

116117

117118
class PublicAPIClient:
@@ -196,7 +197,7 @@ def api_request(
196197
params["preview"] = "true"
197198
else:
198199
params = {"preview": "true"}
199-
return self.context.client.request(
200+
response = self.context.client.request(
200201
method=method,
201202
url=self.api_url(api_path, version=api_version),
202203
params=params,
@@ -213,5 +214,6 @@ def api_request(
213214
verify=verify,
214215
cert=cert,
215216
json=json,
216-
error_handling=error_handling,
217217
)
218+
raise_foundry_api_error(response, error_handling)
219+
return response

libs/foundry-dev-tools/src/foundry_dev_tools/clients/context_client.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@
44

55
import logging
66
import numbers
7-
import os
87
import time
98
import typing
10-
from typing import TYPE_CHECKING, Literal
9+
from typing import TYPE_CHECKING
1110

1211
import requests
1312

14-
from foundry_dev_tools.__about__ import __version__
15-
from foundry_dev_tools.errors.handling import ErrorHandlingConfig, raise_foundry_api_error
16-
1713
if TYPE_CHECKING:
14+
from os import PathLike
15+
1816
from requests import Response
1917
from requests.cookies import RequestsCookieJar
2018
from requests.sessions import ( # type: ignore[attr-defined]
@@ -31,7 +29,6 @@
3129
_Verify,
3230
)
3331

34-
from foundry_dev_tools.config.context import FoundryContext
3532

3633
DEFAULT_TIMEOUT = (60, None)
3734
LOGGER = logging.getLogger(__name__)
@@ -73,14 +70,12 @@ def newfn(*args, **kwargs) -> typing.Callable:
7370
class ContextHTTPClient(requests.Session):
7471
"""Requests Session with config and authentication applied."""
7572

76-
def __init__(self, context: FoundryContext) -> None:
77-
self.context = context
73+
def __init__(self, debug: bool = False, requests_ca_bundle: PathLike[str] | None = None) -> None:
74+
self.debug = debug
75+
self.requests_ca_bundle = requests_ca_bundle
7876
super().__init__()
79-
if self.context.config.requests_ca_bundle:
80-
self.verify = os.fspath(self.context.config.requests_ca_bundle)
81-
self.auth = lambda r: self.context.token_provider.requests_auth_handler(r)
77+
8278
self._counter = 0
83-
self.headers = {"User-Agent": f"foundry-dev-tools/{__version__}/python-requests"}
8479

8580
@retry(times=3, exceptions=requests.exceptions.ConnectionError)
8681
def request(
@@ -101,7 +96,6 @@ def request(
10196
verify: _Verify | None = None,
10297
cert: _Cert | None = None,
10398
json: Incomplete | None = None,
104-
error_handling: ErrorHandlingConfig | Literal[False] | None = None,
10599
) -> Response:
106100
"""Make an authenticated HTTP request.
107101
@@ -122,11 +116,10 @@ def request(
122116
verify: see :py:meth:`requests.Session.request`
123117
cert: see :py:meth:`requests.Session.request`
124118
json: see :py:meth:`requests.Session.request`
125-
error_handling: error handling config; if set to False, errors won't be automatically handled
126119
"""
127-
if verify is None and (rcab := self.context.config.requests_ca_bundle) is not None:
120+
if verify is None and (rcab := self.requests_ca_bundle) is not None:
128121
verify = rcab
129-
if self.context.config.debug:
122+
if self.debug:
130123
self._counter = count = self._counter + 1
131124
LOGGER.debug(f"(r{count}) Making {method!s} request to {url!s}") # noqa: G004
132125

@@ -155,12 +148,11 @@ def request(
155148
cert=cert,
156149
json=json,
157150
)
158-
if self.context.config.debug:
151+
if self.debug:
159152
LOGGER.debug(
160153
f"(r{count}) Got response status={response.status_code}, " # noqa: G004
161154
f"content_type={response.headers.get('content-type')}, "
162155
f"content_length={response.headers.get('content-length')}, "
163156
f"Server-Timing={response.headers.get('Server-Timing')}",
164157
)
165-
raise_foundry_api_error(response, error_handling)
166158
return response

libs/foundry-dev-tools/src/foundry_dev_tools/clients/s3_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ def api_assume_role_with_webidentity(self, expiration_duration: int = 3600) -> r
177177
expiration_duration: seconds the credentials should be valid, defaults to 3600 (the upper bound)
178178
"""
179179
# does not call the api_request method, as this is not a regular api
180-
resp = self.context.client.post(
180+
resp = self.context.client.request(
181+
"POST",
181182
self.get_url(),
182183
params={
183184
"Action": "AssumeRoleWithWebIdentity",

libs/foundry-dev-tools/src/foundry_dev_tools/config/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
from collections.abc import Iterable
3333
from os import PathLike
3434

35+
import requests
36+
3537
# compatibility for python version < 3.11
3638
if sys.version_info < (3, 11):
3739
import tomli as tomllib
@@ -54,6 +56,7 @@ def __init__(
5456
transforms_output_folder: PathLike[str] | None = None,
5557
rich_traceback: bool = False,
5658
debug: bool = False,
59+
requests_session: requests.Session | None = None,
5760
) -> None:
5861
"""Initialize the configuration.
5962
@@ -76,6 +79,7 @@ def __init__(
7679
files are written to this folder.
7780
rich_traceback: enables a prettier traceback provided by the module `rich` See: https://rich.readthedocs.io/en/stable/traceback.html
7881
debug: enables debug logging
82+
requests_session (requests.Session): Overwrite the default used requests.Session.
7983
8084
"""
8185
self.requests_ca_bundle = os.fspath(requests_ca_bundle) if requests_ca_bundle else None
@@ -90,6 +94,7 @@ def __init__(
9094
self.transforms_freeze_cache = bool(transforms_freeze_cache)
9195
self.rich_traceback = bool(rich_traceback)
9296
self.debug = bool(debug)
97+
self.requests_session = requests_session
9398

9499
def __repr__(self) -> str:
95100
return "<" + self.__class__.__name__ + "(" + self.__dict__.__str__() + ")>"

libs/foundry-dev-tools/src/foundry_dev_tools/config/context.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
from __future__ import annotations
44

5+
import os
56
from functools import cached_property, partial
67
from typing import TYPE_CHECKING
78

9+
import requests
10+
11+
from foundry_dev_tools.__about__ import __version__
812
from foundry_dev_tools.clients import (
913
build2,
1014
catalog,
@@ -42,7 +46,7 @@ class FoundryContext:
4246

4347
config: Config
4448
token_provider: TokenProvider
45-
client: context_client.ContextHTTPClient
49+
client: requests.Session
4650

4751
def __init__(
4852
self,
@@ -57,7 +61,21 @@ def __init__(
5761
else:
5862
self.token_provider = token_provider
5963
self.config = config
60-
self.client = context_client.ContextHTTPClient(self)
64+
65+
if not self.config.requests_session:
66+
self.client = context_client.ContextHTTPClient(
67+
debug=self.config.debug, requests_ca_bundle=self.config.requests_ca_bundle
68+
)
69+
else:
70+
self.client = self.config.requests_session
71+
72+
self.client.auth = lambda r: self.token_provider.requests_auth_handler(r)
73+
self.client.headers["User-Agent"] = requests.utils.default_user_agent(
74+
f"foundry-dev-tools/{__version__}/python-requests"
75+
)
76+
if self.config.requests_ca_bundle:
77+
self.verify = os.fspath(self.config.requests_ca_bundle)
78+
6179
if self.config.rich_traceback:
6280
from rich.traceback import install
6381

tests/unit/clients/test_context_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ def test_context_http_client(test_context_mock, foundry_client_id):
2727

2828
req = test_context_mock.client.request("POST", "http+mock://test_authorization").request
2929
assert req.headers["Authorization"] == f"Bearer {test_context_mock.token}"
30-
assert req.headers["User-Agent"] == f"foundry-dev-tools/{__version__}/python-requests"
30+
from requests import __version__ as requests_version
31+
32+
assert req.headers["User-Agent"] == f"foundry-dev-tools/{__version__}/python-requests/{requests_version}"
3133

3234
tokens = ["token_from_oauth", "second_token_from_oauth"]
3335

0 commit comments

Comments
 (0)