From aa65f2d48347c63723aa525539cb02f905fe04bc Mon Sep 17 00:00:00 2001 From: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com> Date: Fri, 11 Oct 2024 04:59:03 -0400 Subject: [PATCH] Support `'if-token-present'` for env var `'LOGFIRE_SEND_TO_LOGFIRE'` (#488) Co-authored-by: Alex Hall --- .vscode/settings.json | 7 +++++++ CONTRIBUTING.md | 11 ++++++----- logfire-api/logfire_api/_internal/config.pyi | 2 +- logfire/_internal/config.py | 2 +- logfire/_internal/config_params.py | 11 +++++++++-- tests/test_configure.py | 20 ++++++++++++++++++++ 6 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a3a18383 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14d76e21..c89e71c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,10 +6,11 @@ We'd love anyone interested to contribute to the Logfire SDK and documentation. 1. Fork and clone the repository 2. [Install uv](https://docs.astral.sh/uv/getting-started/installation/) -3. Run `make install` to install dependencies -4. Run `make test` to run unit tests -5. Run `make format` to format code -6. Run `make lint` to lint code -7. run `make docs` to build docs and `make docs-serve` to serve docs locally +3. [Install pre-commit](https://pre-commit.com/#install) +4. Run `make install` to install dependencies +5. Run `make test` to run unit tests +6. Run `make format` to format code +7. Run `make lint` to lint code +8. run `make docs` to build docs and `make docs-serve` to serve docs locally You're now set up to start contributing! diff --git a/logfire-api/logfire_api/_internal/config.pyi b/logfire-api/logfire_api/_internal/config.pyi index da6253be..6569d023 100644 --- a/logfire-api/logfire_api/_internal/config.pyi +++ b/logfire-api/logfire_api/_internal/config.pyi @@ -145,7 +145,7 @@ class _LogfireConfigData: advanced: AdvancedOptions class LogfireConfig(_LogfireConfigData): - def __init__(self, send_to_logfire: bool | None = None, token: str | None = None, service_name: str | None = None, service_version: str | None = None, console: ConsoleOptions | Literal[False] | None = None, config_dir: Path | None = None, data_dir: Path | None = None, additional_span_processors: Sequence[SpanProcessor] | None = None, metrics: MetricsOptions | Literal[False] | None = None, scrubbing: ScrubbingOptions | Literal[False] | None = None, inspect_arguments: bool | None = None, sampling: SamplingOptions | None = None, code_source: CodeSource | None = None, advanced: AdvancedOptions | None = None) -> None: + def __init__(self, send_to_logfire: bool | Literal['if-token-present'] | None = None, token: str | None = None, service_name: str | None = None, service_version: str | None = None, console: ConsoleOptions | Literal[False] | None = None, config_dir: Path | None = None, data_dir: Path | None = None, additional_span_processors: Sequence[SpanProcessor] | None = None, metrics: MetricsOptions | Literal[False] | None = None, scrubbing: ScrubbingOptions | Literal[False] | None = None, inspect_arguments: bool | None = None, sampling: SamplingOptions | None = None, code_source: CodeSource | None = None, advanced: AdvancedOptions | None = None) -> None: """Create a new LogfireConfig. Users should never need to call this directly, instead use `logfire.configure`. diff --git a/logfire/_internal/config.py b/logfire/_internal/config.py index 2e63be52..3d8fdd02 100644 --- a/logfire/_internal/config.py +++ b/logfire/_internal/config.py @@ -551,7 +551,7 @@ def _load_configuration( class LogfireConfig(_LogfireConfigData): def __init__( self, - send_to_logfire: bool | None = None, + send_to_logfire: bool | Literal['if-token-present'] | None = None, token: str | None = None, service_name: str | None = None, service_version: str | None = None, diff --git a/logfire/_internal/config_params.py b/logfire/_internal/config_params.py index 4a346f0e..1ae70c94 100644 --- a/logfire/_internal/config_params.py +++ b/logfire/_internal/config_params.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from functools import cached_property from pathlib import Path -from typing import Any, Callable, Literal, Set, TypeVar +from typing import Any, Callable, Literal, Set, TypeVar, Union from opentelemetry.sdk.environment_variables import OTEL_SERVICE_NAME from typing_extensions import get_args, get_origin @@ -53,7 +53,7 @@ class _DefaultCallback: """When running under pytest, don't send spans to Logfire by default.""" # fmt: off -SEND_TO_LOGFIRE = ConfigParam(env_vars=['LOGFIRE_SEND_TO_LOGFIRE'], allow_file_config=True, default=_send_to_logfire_default, tp=bool) +SEND_TO_LOGFIRE = ConfigParam(env_vars=['LOGFIRE_SEND_TO_LOGFIRE'], allow_file_config=True, default=_send_to_logfire_default, tp=Union[bool, Literal['if-token-present']]) """Whether to send spans to Logfire.""" TOKEN = ConfigParam(env_vars=['LOGFIRE_TOKEN']) """Token for the Logfire API.""" @@ -183,6 +183,13 @@ def _cast(self, value: Any, name: str, tp: type[T]) -> T | None: return value if get_origin(tp) is Literal: return _check_literal(value, name, tp) + if get_origin(tp) is Union: + for arg in get_args(tp): + try: + return self._cast(value, name, arg) + except LogfireConfigError: + pass + raise LogfireConfigError(f'Expected {name} to be an instance of one of {get_args(tp)}, got {value!r}') if tp is bool: return _check_bool(value, name) # type: ignore if tp is float: diff --git a/tests/test_configure.py b/tests/test_configure.py index d85acd10..63c17688 100644 --- a/tests/test_configure.py +++ b/tests/test_configure.py @@ -454,6 +454,14 @@ def test_read_config_from_environment_variables() -> None: ): fresh_pydantic_plugin() + with patch.dict(os.environ, {'LOGFIRE_SEND_TO_LOGFIRE': 'not-valid'}): + with inline_snapshot.extra.raises( + snapshot( + "LogfireConfigError: Expected send_to_logfire to be an instance of one of (, typing.Literal['if-token-present']), got 'not-valid'" + ) + ): + configure() + assert fresh_pydantic_plugin().include == set() with patch.dict(os.environ, {'LOGFIRE_PYDANTIC_PLUGIN_INCLUDE': 'test'}): assert fresh_pydantic_plugin().include == {'test'} @@ -1254,6 +1262,18 @@ def test_send_to_logfire_if_token_present_empty() -> None: del os.environ['LOGFIRE_TOKEN'] +def test_send_to_logfire_if_token_present_empty_via_env_var() -> None: + with patch.dict( + os.environ, + {'LOGFIRE_TOKEN': '', 'LOGFIRE_SEND_TO_LOGFIRE': 'if-token-present'}, + ), mock.patch( + 'logfire._internal.config.Confirm.ask', + side_effect=RuntimeError, + ), requests_mock.Mocker() as requests_mocker: + configure(console=False) + assert len(requests_mocker.request_history) == 0 + + def wait_for_check_token_thread(): for thread in threading.enumerate(): if thread.name == 'check_logfire_token': # pragma: no cover