Skip to content

Commit

Permalink
FFM-11935 Add with_httpx_args option (#106)
Browse files Browse the repository at this point in the history
* {client,config}: allow passing through additional httpx arguments (#105)

* FFM-11935 Fix line length for flake8 / Update docs and add sample.

* FFM-11935 Update docstring and flake8 formatting change

---------

Co-authored-by: Zane van Iperen <[email protected]>
  • Loading branch information
erdirowlands and zane-zeroflucs authored Aug 27, 2024
1 parent a5e9a1f commit d32a599
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 10 deletions.
32 changes: 32 additions & 0 deletions docs/further_reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,35 @@ Then pass them with the new URLs when creating your client.

[Example](../examples/url_change_example/url_change.py)

## HTTPX Configuration Options
The `httpx` client allows you to apply various configurations to all outgoing requests by passing parameters to the Client constructor.

Here are some of the options you can configure:

* `proxies`: Configure proxy settings to route requests through a specific proxy server.
* `headers`: Add custom headers to all outgoing requests.
* `timeout`: Set request and connection timeouts.
* `transport`: Use a custom transport layer for advanced scenarios.

**Important**: ensure you supply a valid httpx option. if you supply an option that doesn't exist in `httpx` the SDK will fail to initialize with `got an unexpected keyword argument`

Further Reading:

* [HTTPX Advanced Client Configuration](https://www.python-httpx.org/advanced/clients/)

Example of Custom HTTPX Client Configuration

Here's an example demonstrating how to use httpx client arguments in the SDK:


```python
from featureflags.config import with_httpx_args

client = CfClient(api_key,
with_base_url("https://config.ff.harness.io/api/1.0"),
with_httpx_args({
'proxies': 'http://localhost:8888'}
}))
```

[Example](../examples/with_httpx_args_example/with_httpx_args.py)
35 changes: 35 additions & 0 deletions examples/with_httpx_args_example/with_httpx_args.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logging
import time

from featureflags.config import with_httpx_args, with_base_url
from featureflags.evaluations.auth_target import Target
from featureflags.client import CfClient
from featureflags.util import log


def main():
log.setLevel(logging.INFO)
log.info("Starting example")
api_key = "Your API key"

# Using the httpx proxies option.
# Ensure you supply a valid httpx option. if you supply an option that
# doesn't exist in `httpx` the SDK will fail to initialize with `got an
# unexpected keyword argument`
client = CfClient(api_key,
with_httpx_args({'proxies': 'http://localhost:8888'}))

client.wait_for_initialization()

target = Target(identifier='HT_1', name="Harness_Target_1",
attributes={"location": "emea"})

while True:
result = client.bool_variation('identifier_of_your_bool_flag', target,
False)
log.info("Result %s", result)
time.sleep(10)


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion featureflags/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

__author__ = """Harness"""
__email__ = "[email protected]"
__version__ = '1.6.4'
__version__ = '1.7.0'
2 changes: 1 addition & 1 deletion featureflags/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
VARIATION_VALUE_ATTRIBUTE = 'variationValue'
TARGET_ATTRIBUTE = 'target'
SDK_VERSION_ATTRIBUTE = 'SDK_VERSION'
SDK_VERSION = '1.6.4'
SDK_VERSION = '1.7.0'
SDK_TYPE_ATTRIBUTE = 'SDK_TYPE'
SDK_TYPE = 'server'
SDK_LANGUAGE_ATTRIBUTE = 'SDK_LANGUAGE'
Expand Down
8 changes: 5 additions & 3 deletions featureflags/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .streaming import StreamProcessor
from .util import log

VERSION: str = "1.6.4"
VERSION: str = "1.7.0"


class MissingOrEmptyAPIKeyException(Exception):
Expand Down Expand Up @@ -177,7 +177,8 @@ def authenticate(self):
verify = self._config.tls_trusted_cas_file

client = Client(base_url=self._config.base_url, verify_ssl=verify,
raise_on_unexpected_status=True)
raise_on_unexpected_status=True,
httpx_args=self._config.httpx_args)
body = AuthenticationRequest(api_key=self._sdk_key)
response = retryable_authenticate(client=client, body=body).parsed
self._auth_token = response.auth_token
Expand Down Expand Up @@ -206,7 +207,8 @@ def make_client(self, url, token, account_id, config):
client = AuthenticatedClient(
base_url=url,
token=token,
verify_ssl=verify
verify_ssl=verify,
httpx_args=self._config.httpx_args,
)
# Additional headers used to track usage
additional_headers = {
Expand Down
23 changes: 21 additions & 2 deletions featureflags/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Configuration is a base class that has default values that you can change
during the instance of the client class"""

from typing import Callable
from typing import Any, Callable, Dict

from .interface import Cache
from .lru_cache import LRUCache
Expand All @@ -28,7 +28,8 @@ def __init__(
enable_stream: bool = True,
enable_analytics: bool = True,
max_auth_retries: int = 10,
tls_trusted_cas_file: str = None
tls_trusted_cas_file: str = None,
httpx_args: Dict[str, Any] = None,
):
self.base_url = base_url
self.events_url = events_url
Expand All @@ -49,6 +50,9 @@ def __init__(
self.enable_analytics = enable_analytics
self.max_auth_retries = max_auth_retries
self.tls_trusted_cas_file = tls_trusted_cas_file
self.httpx_args = httpx_args
if self.httpx_args is None:
self.httpx_args = {}


default_config = Config()
Expand Down Expand Up @@ -102,7 +106,22 @@ def with_tls_trusted_cas_file(value: str) -> Callable:
It takes a filename of a CA bundle. It should include all intermediate CAs
and the root CA (concatenated in PEM format).
"""

def func(config: Config) -> None:
config.tls_trusted_cas_file = value

return func


"""
Allows the user to pass additional arguments to the HTTPx client
configuration, such as proxies, timeouts, or custom headers. See
https://www.python-httpx.org/advanced/clients/ for further information.
"""


def with_httpx_args(args: Dict[str, Any]) -> Callable:
def func(config: Config) -> None:
config.httpx_args.update(args)

return func
3 changes: 2 additions & 1 deletion featureflags/streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ def run(self):
cluster=self._cluster).parsed

if fc is None:
log.debug("Feature config '%s' not loaded", self._msg.identifier)
log.debug("Feature config '%s' not loaded",
self._msg.identifier)
else:
log.debug("Feature config '%s' loaded", fc.feature)
self._repository.set_flag(fc)
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.6.4
current_version = 1.7.0
commit = True
tag = True

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@
test_suite="tests",
tests_require=test_requirements,
url="https://github.com/harness/ff-python-server-sdk",
version='1.6.4',
version='1.7.0',
zip_safe=False,
)

0 comments on commit d32a599

Please sign in to comment.