diff --git a/README.md b/README.md index 583de91..d75c2cc 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,26 @@ The SDK for Python applications for [https://www.flagsmith.com/](https://www.fla For full documentation visit [https://docs.flagsmith.com/clients/server-side?language=python](https://docs.flagsmith.com/clients/server-side?language=python). +## Checking client readiness + +`flagsmith.ready()` returns whether the client has an environment document available for local or offline evaluation. It +makes no network requests and does not block. Remote-evaluation clients return `True` immediately; readiness does not +indicate API connectivity or document freshness. + +If the initial local-evaluation fetch fails and no document is available, clients with realtime updates disabled are not +ready until the background poller loads a document. With realtime updates enabled, construction raises `ValueError` +instead. A later refresh failure keeps the last successful document available, so readiness remains `True`. + +Check readiness before evaluating flags to avoid the existing remote API fallback while the first local document is +unavailable: + +```python +if flagsmith.ready(): + enabled = flagsmith.get_environment_flags().is_feature_enabled("feature") +else: + enabled = False # Application-defined fallback while waiting for a document. +``` + ## Contributing Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index f50ceec..9328011 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -275,6 +275,18 @@ def handle_stream_event(self, event: StreamEvent) -> None: if event["updated_at"] > environment_updated_at: self.update_environment() + def ready(self) -> bool: + """Return whether the client can evaluate in its configured mode. + + Local and offline evaluation require an environment document. Remote + evaluation is immediately ready; this does not check API connectivity. + This method does not make requests or wait for a document to load. + """ + return ( + not (self.enable_local_evaluation or self.offline_mode) + or self._local_evaluation is not None + ) + def get_environment_flags(self) -> Flags: """ Get all the default for flags for the current environment. diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 7998c23..0b79fee 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -23,6 +23,41 @@ from flagsmith.types import SDKEvaluationContext +@responses.activate() +def test_ready_tracks_local_document_availability( + mocker: MockerFixture, environment_json: str +) -> None: + mocker.patch("flagsmith.flagsmith.EnvironmentDataPollingManager") + url = "https://edge.api.flagsmith.com/api/v1/environment-document/" + responses.get(url, status=500) + responses.get(url, body=environment_json) + responses.get(url, status=500) + client = Flagsmith(environment_key="ser.test", enable_local_evaluation=True) + + assert client.ready() is False + assert len(responses.calls) == 1 + client.update_environment() + assert client.ready() is True + client.update_environment() + assert client.ready() is True + assert len(responses.calls) == 3 + + +def test_ready_remote_evaluation(flagsmith: Flagsmith) -> None: + assert flagsmith.ready() is True + + +def test_ready_offline_document( + mocker: MockerFixture, environment: EnvironmentModel +) -> None: + handler = mocker.Mock(spec=OfflineHandler) + handler.get_environment.return_value = environment + client = Flagsmith(offline_mode=True, offline_handler=handler) + + assert client.ready() is True + assert client.get_environment_flags().is_feature_enabled("some_feature") + + def test_flagsmith_starts_polling_manager_on_init_if_enabled( mocker: MockerFixture, server_api_key: str, requests_session_response_ok: None ) -> None: