Skip to content

Conversation

@dmontagu
Copy link

This PR

Updates the OFREP provider to use httpx and have proper async support.

I have not yet added tests etc. to this PR, because I wanted to get feedback on it before doing more work. But I have used this version of the provider code internally and it has worked fine, so I at least wanted to share.

Related Issues

Closes #310 if merged.

Follow-up Tasks

Need to update pyproject.toml, add tests, etc. Probably will take a decent effort so I don't want to undertake it if there's not consensus this is a good approach.

I'd be potentially willing to port all existing usage of requests to httpx if there was interest in expanding beyond just this OFREP provider (and with that, adding async support for all providers in this package). I'm not sure what the right replacement for requests-mock is with httpx (which I can see is what is used in tests) but I'm sure something reasonable exists.

How to test

This won't work yet unless you manually (uv) pip install httpx. I'm mostly looking to get confirmation of interest in the approach / replacing requests with httpx at this time.

@dmontagu dmontagu changed the title Update ofrep provider to use httpx and support async Update OFREP provider to use httpx and support async Oct 28, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 28, 2025

Hey there and thank you for opening this pull request! 👋🏼

We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted.

Details:

No release type found in pull request title "Update OFREP provider to use httpx and support async". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/

Available types:
 - feat: A new feature
 - fix: A bug fix
 - docs: Documentation only changes
 - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
 - refactor: A code change that neither fixes a bug nor adds a feature
 - perf: A code change that improves performance
 - test: Adding missing tests or correcting existing tests
 - build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
 - ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
 - chore: Other changes that don't modify src or test files
 - revert: Reverts a previous commit

Copy link
Member

@gruebel gruebel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the contribution 🍻 I don't mind switching to httpx, but please add some test case(s) for the async variant and you also need to adjust the pyproject.toml

Comment on lines +64 to +65
def initialize(self, evaluation_context: EvaluationContext) -> None:
self.client.__enter__()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why enter the client here? when you initialize it in the constructor then it should be already entered or not?

self.client.__enter__()

def shutdown(self) -> None:
self.client.__exit__(None, None, None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just call .close() on it


try:
# TODO(someday): support non asyncio runtimes here
asyncio.get_running_loop().create_task(self.client_async.__aexit__(None, None, None))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here aclose()

Comment on lines +258 to +260
if not self._client_async_is_entered:
await self.client_async.__aenter__()
self._client_async_is_entered = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as far as I can tell from the official docs this is not needed

Comment on lines +262 to +268
now = datetime.now(timezone.utc)
if self.retry_after and now <= self.retry_after:
raise GeneralError(
f"OFREP evaluation paused due to TooManyRequests until {self.retry_after}"
)
elif self.retry_after:
self.retry_after = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated code, please create a small private method for it

Comment on lines +282 to +294
try:
data = response.json()
except JSONDecodeError as e:
raise ParseError(str(e)) from e

_typecheck_flag_value(data["value"], flag_type)

return FlagResolutionDetails(
value=data["value"],
reason=Reason[data["reason"]],
variant=data["variant"],
flag_metadata=data.get("metadata", {}),
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for this

Comment on lines +331 to +333
def __del__(self):
# Ensure clients get cleaned up
self.shutdown()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure, if this is really needed, but it is better to use weakref for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support async in OFREP provider

2 participants