From 15718a038163e2432d5dea64b50c15bee116cf72 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Tue, 7 Jan 2025 12:00:39 +0200 Subject: [PATCH] Release v3.0.0 (#777) --- CHANGELOG.md | 13 +++ logfire-api/logfire_api/_internal/cli.pyi | 6 ++ logfire-api/logfire_api/_internal/config.pyi | 14 ++- .../logfire_api/_internal/config_params.pyi | 1 + .../_internal/integrations/httpx.pyi | 92 ++++++++----------- .../_internal/integrations/mysql.pyi | 13 +-- .../_internal/integrations/psycopg.pyi | 23 ++--- .../_internal/integrations/pymongo.pyi | 24 +---- .../_internal/integrations/redis.pyi | 21 +---- .../_internal/integrations/sqlalchemy.pyi | 15 +-- logfire-api/logfire_api/_internal/main.pyi | 77 +++++++--------- logfire-api/logfire_api/_internal/tracer.pyi | 9 +- .../logfire_api/integrations/psycopg.pyi | 10 ++ .../logfire_api/integrations/redis.pyi | 26 ++++++ .../logfire_api/integrations/sqlalchemy.pyi | 7 ++ logfire-api/logfire_api/propagate.pyi | 35 ++++++- logfire-api/pyproject.toml | 2 +- pyproject.toml | 2 +- uv.lock | 13 +-- 19 files changed, 218 insertions(+), 185 deletions(-) create mode 100644 logfire-api/logfire_api/integrations/psycopg.pyi create mode 100644 logfire-api/logfire_api/integrations/redis.pyi create mode 100644 logfire-api/logfire_api/integrations/sqlalchemy.pyi diff --git a/CHANGELOG.md b/CHANGELOG.md index 780f5bc54..b6014621c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Release Notes +## [v3.0.0] (2025-01-07) + +* **BREAKING CHANGE**: Removed `capture_request_json_body`, `capture_request_text_body`, `capture_request_form_data`, and `capture_response_json_body` parameters from `logfire.instrument_httpx()`, replaced with `capture_request_body` `capture_response_body` by @Kludex in [#769](https://github.com/pydantic/logfire/pull/769) + +Other changes: + +* Add `distributed_tracing` argument to `logfire.configure()` and warn by default when trace context is extracted by @alexmojaki in [#773](https://github.com/pydantic/logfire/pull/773) +* Don't show `urllib3` when `requests` is installed on `logfire inspect` by @Kludex in [#744](https://github.com/pydantic/logfire/pull/744) +* Add `--ignore` to `logfire inspect` by @Kludex in [#748](https://github.com/pydantic/logfire/pull/748) +* Access `model_fields` on the model class by @Viicos in [#761](https://github.com/pydantic/logfire/pull/761) +* Remove double record exception by @dmontagu in [#712](https://github.com/pydantic/logfire/pull/712) + ## [v2.11.1] (2024-12-30) * Handle errors from `sqlalchemy.inspect` by @alexmojaki in [#733](https://github.com/pydantic/logfire/pull/733) @@ -504,3 +516,4 @@ First release from new repo! [v2.10.0]: https://github.com/pydantic/logfire/compare/v2.9.0...v2.10.0 [v2.11.0]: https://github.com/pydantic/logfire/compare/v2.10.0...v2.11.0 [v2.11.1]: https://github.com/pydantic/logfire/compare/v2.11.0...v2.11.1 +[v3.0.0]: https://github.com/pydantic/logfire/compare/v2.11.1...v3.0.0 diff --git a/logfire-api/logfire_api/_internal/cli.pyi b/logfire-api/logfire_api/_internal/cli.pyi index 895874136..75a8fe978 100644 --- a/logfire-api/logfire_api/_internal/cli.pyi +++ b/logfire-api/logfire_api/_internal/cli.pyi @@ -9,6 +9,7 @@ from .utils import read_toml_file as read_toml_file from _typeshed import Incomplete from logfire.exceptions import LogfireConfigError as LogfireConfigError from logfire.propagate import ContextCarrier as ContextCarrier, get_context as get_context +from typing import Any, Sequence BASE_OTEL_INTEGRATION_URL: str BASE_DOCS_URL: str @@ -24,6 +25,7 @@ def parse_whoami(args: argparse.Namespace) -> None: def parse_clean(args: argparse.Namespace) -> None: """Remove the contents of the Logfire data directory.""" +STANDARD_LIBRARY_PACKAGES: Incomplete OTEL_PACKAGES: set[str] OTEL_PACKAGE_LINK: Incomplete @@ -42,5 +44,9 @@ def parse_use_project(args: argparse.Namespace) -> None: """Use an existing project.""" def parse_info(_args: argparse.Namespace) -> None: """Show versions of logfire, OS and related packages.""" + +class SplitArgs(argparse.Action): + def __call__(self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, values: str | Sequence[Any] | None, option_string: str | None = None): ... + def main(args: list[str] | None = None) -> None: """Run the CLI.""" diff --git a/logfire-api/logfire_api/_internal/config.pyi b/logfire-api/logfire_api/_internal/config.pyi index 20e276f27..35004886c 100644 --- a/logfire-api/logfire_api/_internal/config.pyi +++ b/logfire-api/logfire_api/_internal/config.pyi @@ -1,5 +1,6 @@ import dataclasses import requests +from ..propagate import NoExtractTraceContextPropagator as NoExtractTraceContextPropagator, WarnOnExtractTraceContextPropagator as WarnOnExtractTraceContextPropagator from .auth import DEFAULT_FILE as DEFAULT_FILE, DefaultFile as DefaultFile, is_logged_in as is_logged_in from .config_params import ParamManager as ParamManager, PydanticPluginRecordValues as PydanticPluginRecordValues from .constants import LevelName as LevelName, OTLP_MAX_BODY_SIZE as OTLP_MAX_BODY_SIZE, RESOURCE_ATTRIBUTES_CODE_ROOT_PATH as RESOURCE_ATTRIBUTES_CODE_ROOT_PATH, RESOURCE_ATTRIBUTES_CODE_WORK_DIR as RESOURCE_ATTRIBUTES_CODE_WORK_DIR, RESOURCE_ATTRIBUTES_DEPLOYMENT_ENVIRONMENT_NAME as RESOURCE_ATTRIBUTES_DEPLOYMENT_ENVIRONMENT_NAME, RESOURCE_ATTRIBUTES_VCS_REPOSITORY_REF_REVISION as RESOURCE_ATTRIBUTES_VCS_REPOSITORY_REF_REVISION, RESOURCE_ATTRIBUTES_VCS_REPOSITORY_URL as RESOURCE_ATTRIBUTES_VCS_REPOSITORY_URL @@ -78,7 +79,7 @@ class CodeSource: class DeprecatedKwargs(TypedDict): ... -def configure(*, local: bool = False, send_to_logfire: bool | Literal['if-token-present'] | None = None, token: str | None = None, service_name: str | None = None, service_version: str | None = None, environment: str | None = None, console: ConsoleOptions | Literal[False] | None = None, config_dir: Path | str | None = None, data_dir: Path | str | 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, **deprecated_kwargs: Unpack[DeprecatedKwargs]) -> Logfire: +def configure(*, local: bool = False, send_to_logfire: bool | Literal['if-token-present'] | None = None, token: str | None = None, service_name: str | None = None, service_version: str | None = None, environment: str | None = None, console: ConsoleOptions | Literal[False] | None = None, config_dir: Path | str | None = None, data_dir: Path | str | 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, distributed_tracing: bool | None = None, advanced: AdvancedOptions | None = None, **deprecated_kwargs: Unpack[DeprecatedKwargs]) -> Logfire: """Configure the logfire SDK. Args: @@ -127,6 +128,12 @@ def configure(*, local: bool = False, send_to_logfire: bool | Literal['if-token- sampling: Sampling options. See the [sampling guide](https://logfire.pydantic.dev/docs/guides/advanced/sampling/). code_source: Settings for the source code of the project. + distributed_tracing: By default, incoming trace context is extracted, but generates a warning. + Set to `True` to disable the warning. + Set to `False` to suppress extraction of incoming trace context. + See [Unintentional Distributed Tracing](https://logfire.pydantic.dev/docs/how-to-guides/distributed-tracing/#unintentional-distributed-tracing) + for more information. + This setting always applies globally, and the last value set is used, including the default value. advanced: Advanced options primarily used for testing by Logfire developers. """ @@ -153,17 +160,18 @@ class _LogfireConfigData: inspect_arguments: bool sampling: SamplingOptions code_source: CodeSource | None + distributed_tracing: bool | None advanced: AdvancedOptions class LogfireConfig(_LogfireConfigData): - 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, environment: 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, environment: 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, distributed_tracing: bool | None = None, advanced: AdvancedOptions | None = None) -> None: """Create a new LogfireConfig. Users should never need to call this directly, instead use `logfire.configure`. See `_LogfireConfigData` for parameter documentation. """ - def configure(self, send_to_logfire: bool | Literal['if-token-present'] | None, token: str | None, service_name: str | None, service_version: str | None, environment: str | None, console: ConsoleOptions | Literal[False] | None, config_dir: Path | None, data_dir: Path | None, additional_span_processors: Sequence[SpanProcessor] | None, metrics: MetricsOptions | Literal[False] | None, scrubbing: ScrubbingOptions | Literal[False] | None, inspect_arguments: bool | None, sampling: SamplingOptions | None, code_source: CodeSource | None, advanced: AdvancedOptions | None) -> None: ... + def configure(self, send_to_logfire: bool | Literal['if-token-present'] | None, token: str | None, service_name: str | None, service_version: str | None, environment: str | None, console: ConsoleOptions | Literal[False] | None, config_dir: Path | None, data_dir: Path | None, additional_span_processors: Sequence[SpanProcessor] | None, metrics: MetricsOptions | Literal[False] | None, scrubbing: ScrubbingOptions | Literal[False] | None, inspect_arguments: bool | None, sampling: SamplingOptions | None, code_source: CodeSource | None, distributed_tracing: bool | None, advanced: AdvancedOptions | None) -> None: ... def initialize(self) -> None: """Configure internals to start exporting traces and metrics.""" def force_flush(self, timeout_millis: int = 30000) -> bool: diff --git a/logfire-api/logfire_api/_internal/config_params.pyi b/logfire-api/logfire_api/_internal/config_params.pyi index 19d211663..a87117402 100644 --- a/logfire-api/logfire_api/_internal/config_params.pyi +++ b/logfire-api/logfire_api/_internal/config_params.pyi @@ -49,6 +49,7 @@ TRACE_SAMPLE_RATE: Incomplete INSPECT_ARGUMENTS: Incomplete IGNORE_NO_CONFIG: Incomplete BASE_URL: Incomplete +DISTRIBUTED_TRACING: Incomplete CONFIG_PARAMS: Incomplete @dataclass diff --git a/logfire-api/logfire_api/_internal/integrations/httpx.pyi b/logfire-api/logfire_api/_internal/integrations/httpx.pyi index 56cab73cf..be098f224 100644 --- a/logfire-api/logfire_api/_internal/integrations/httpx.pyi +++ b/logfire-api/logfire_api/_internal/integrations/httpx.pyi @@ -1,90 +1,74 @@ import httpx from _typeshed import Incomplete +from collections.abc import Generator from email.headerregistry import ContentTypeHeader -from logfire import Logfire as Logfire +from functools import cached_property +from logfire import Logfire as Logfire, LogfireSpan as LogfireSpan from logfire._internal.main import set_user_attributes_on_raw_span as set_user_attributes_on_raw_span from logfire._internal.stack_info import warn_at_user_stacklevel as warn_at_user_stacklevel from logfire._internal.utils import handle_internal_errors as handle_internal_errors from logfire.integrations.httpx import AsyncRequestHook as AsyncRequestHook, AsyncResponseHook as AsyncResponseHook, RequestHook as RequestHook, RequestInfo as RequestInfo, ResponseHook as ResponseHook, ResponseInfo as ResponseInfo -from logfire.propagate import attach_context as attach_context, get_context as get_context from opentelemetry.trace import Span -from typing import Any, Callable, Literal, Mapping, ParamSpec, TypeVar, TypedDict +from typing import Any, Awaitable, Callable, Literal, Mapping, ParamSpec -class AsyncClientKwargs(TypedDict, total=False): - request_hook: RequestHook | AsyncRequestHook - response_hook: ResponseHook | AsyncResponseHook - skip_dep_check: bool - -class ClientKwargs(TypedDict, total=False): - request_hook: RequestHook - response_hook: ResponseHook - skip_dep_check: bool - -class HTTPXInstrumentKwargs(TypedDict, total=False): - request_hook: RequestHook - response_hook: ResponseHook - async_request_hook: AsyncRequestHook - async_response_hook: AsyncResponseHook - skip_dep_check: bool -AnyRequestHook = TypeVar('AnyRequestHook', RequestHook, AsyncRequestHook) -AnyResponseHook = TypeVar('AnyResponseHook', ResponseHook, AsyncResponseHook) -Hook = TypeVar('Hook', RequestHook, ResponseHook) -AsyncHook = TypeVar('AsyncHook', AsyncRequestHook, AsyncResponseHook) P = ParamSpec('P') -def instrument_httpx(logfire_instance: Logfire, client: httpx.Client | httpx.AsyncClient | None, capture_headers: bool, capture_request_json_body: bool, capture_request_text_body: bool, capture_response_json_body: bool, capture_request_form_data: bool, **kwargs: Any) -> None: +def instrument_httpx(logfire_instance: Logfire, client: httpx.Client | httpx.AsyncClient | None, capture_headers: bool, capture_request_body: bool, capture_response_body: bool, request_hook: RequestHook | AsyncRequestHook | None, response_hook: ResponseHook | AsyncResponseHook | None, async_request_hook: AsyncRequestHook | None, async_response_hook: AsyncResponseHook | None, **kwargs: Any) -> None: """Instrument the `httpx` module so that spans are automatically created for each request. See the `Logfire.instrument_httpx` method for details. """ -class LogfireHttpxRequestInfo(RequestInfo): - span: Span - def capture_headers(self) -> None: ... - def capture_body_if_json(self, attr_name: str = 'http.request.body.json'): ... - def capture_body_if_text(self, attr_name: str = 'http.request.body.text'): ... - def capture_body_if_form(self, attr_name: str = 'http.request.body.form'): ... - def capture_text_as_json(self, attr_name: str = 'http.request.body.json', text: str | None = None): ... - @property - def body_is_streaming(self): ... +class LogfireHttpxInfoMixin: + headers: httpx.Headers @property def content_type_header_object(self) -> ContentTypeHeader: ... @property def content_type_header_string(self) -> str: ... + +class LogfireHttpxRequestInfo(RequestInfo, LogfireHttpxInfoMixin): + span: Span + def capture_headers(self) -> None: ... + def capture_body(self) -> None: ... + def capture_body_if_text(self, attr_name: str = 'http.request.body.text'): ... + def capture_body_if_form(self, attr_name: str = 'http.request.body.form') -> bool: ... + def capture_text_as_json(self, attr_name: str, text: str): ... @property - def content_type_is_json(self): ... - @property - def content_type_is_text(self): ... - @property - def content_type_is_form(self): ... + def body_is_streaming(self): ... @property def content_type_charset(self): ... @property def content(self) -> bytes: ... - @property - def text(self) -> str: ... - @property + @cached_property def form_data(self) -> Mapping[str, Any] | None: ... def set_complex_span_attributes(self, attributes: dict[str, Any]): ... -def make_request_hook(hook: RequestHook | None, should_capture_headers: bool, should_capture_json: bool, should_capture_text: bool, should_capture_form_data: bool) -> RequestHook | None: ... -def make_async_request_hook(hook: AsyncRequestHook | RequestHook | None, should_capture_headers: bool, should_capture_json: bool, should_capture_text: bool, should_capture_form_data: bool) -> AsyncRequestHook | None: ... -def capture_request(request: LogfireHttpxRequestInfo, should_capture_headers: bool, should_capture_json: bool, should_capture_text: bool, should_capture_form_data: bool) -> None: ... -def make_response_hook(hook: ResponseHook | None, should_capture_headers: bool, should_capture_json: bool, logfire_instance: Logfire) -> ResponseHook | None: ... -def make_async_response_hook(hook: ResponseHook | AsyncResponseHook | None, should_capture_headers: bool, should_capture_json: bool, logfire_instance: Logfire) -> AsyncResponseHook | None: ... -def capture_response_json(logfire_instance: Logfire, response_info: ResponseInfo, is_async: bool) -> None: ... +class LogfireHttpxResponseInfo(ResponseInfo, LogfireHttpxInfoMixin): + span: Span + logfire_instance: Logfire + is_async: bool + def capture_headers(self) -> None: ... + def capture_body_if_text(self, attr_name: str = 'http.response.body.text'): ... + @cached_property + def response(self) -> httpx.Response: ... + def on_response_read(self, hook: Callable[[LogfireSpan], None]): ... + def wrap_response_read(self, hook: Callable[[Callable[[], bytes]], bytes]): ... + def wrap_response_aread(self, hook: Callable[[Callable[[], Awaitable[bytes]]], Awaitable[bytes]]): ... + def attach_original_span_context(self) -> Generator[None]: ... + def capture_text_as_json(self, span: LogfireSpan, *, text: str, attr_name: str): ... + +def make_request_hook(hook: RequestHook | None, capture_headers: bool, capture_body: bool) -> RequestHook | None: ... +def make_async_request_hook(hook: AsyncRequestHook | RequestHook | None, should_capture_headers: bool, should_capture_body: bool) -> AsyncRequestHook | None: ... +def make_response_hook(hook: ResponseHook | None, capture_headers: bool, capture_body: bool, logfire_instance: Logfire) -> ResponseHook | None: ... +def make_async_response_hook(hook: ResponseHook | AsyncResponseHook | None, should_capture_headers: bool, should_capture_body: bool, logfire_instance: Logfire) -> AsyncResponseHook | None: ... +def capture_request(span: Span, request: RequestInfo, should_capture_headers: bool, should_capture_body: bool) -> LogfireHttpxRequestInfo: ... +def capture_response(span: Span, request: RequestInfo, response: ResponseInfo, logfire_instance: Logfire, capture_headers: bool, capture_body: bool, *, is_async: bool) -> tuple[LogfireHttpxRequestInfo, LogfireHttpxResponseInfo]: ... async def run_async_hook(hook: Callable[P, Any] | None, *args: P.args, **kwargs: P.kwargs) -> None: ... def run_hook(hook: Callable[P, Any] | None, *args: P.args, **kwargs: P.kwargs) -> None: ... -def capture_response_headers(span: Span, response: ResponseInfo) -> None: ... -def capture_headers(span: Span, headers: httpx.Headers, request_or_response: Literal['request', 'response']) -> None: ... -def decode_body(body: bytes, charset: str): ... +def capture_request_or_response_headers(span: Span, headers: httpx.Headers, request_or_response: Literal['request', 'response']) -> None: ... CODES_FOR_METHODS_WITH_DATA_PARAM: Incomplete def content_type_header_from_string(content_type: str) -> ContentTypeHeader: ... def content_type_subtypes(subtype: str) -> set[str]: ... def is_json_type(content_type: str) -> bool: ... - -TEXT_SUBTYPES: Incomplete - -def is_text_type(content_type: str) -> bool: ... diff --git a/logfire-api/logfire_api/_internal/integrations/mysql.pyi b/logfire-api/logfire_api/_internal/integrations/mysql.pyi index 7f17af613..6e1462a94 100644 --- a/logfire-api/logfire_api/_internal/integrations/mysql.pyi +++ b/logfire-api/logfire_api/_internal/integrations/mysql.pyi @@ -1,14 +1,11 @@ -from mysql.connector.abstracts import MySQLConnectionAbstract -from mysql.connector.pooling import PooledMySQLConnection +from mysql.connector.abstracts import MySQLConnectionAbstract as MySQLConnectionAbstract +from mysql.connector.pooling import PooledMySQLConnection as PooledMySQLConnection from opentelemetry.trace import TracerProvider -from typing_extensions import TypeVar, TypedDict, Unpack +from typing import Any, TypeVar -MySQLConnection = TypeVar('MySQLConnection', bound=PooledMySQLConnection | MySQLConnectionAbstract | None) +MySQLConnection = TypeVar('MySQLConnection', 'PooledMySQLConnection | MySQLConnectionAbstract', None) -class MySQLInstrumentKwargs(TypedDict, total=False): - skip_dep_check: bool - -def instrument_mysql(*, conn: MySQLConnection = None, tracer_provider: TracerProvider, **kwargs: Unpack[MySQLInstrumentKwargs]) -> MySQLConnection: +def instrument_mysql(*, conn: MySQLConnection = None, tracer_provider: TracerProvider, **kwargs: Any) -> MySQLConnection: """Instrument the `mysql` module or a specific MySQL connection so that spans are automatically created for each operation. See the `Logfire.instrument_mysql` method for details. diff --git a/logfire-api/logfire_api/_internal/integrations/psycopg.pyi b/logfire-api/logfire_api/_internal/integrations/psycopg.pyi index 5695f5cf8..9736609f9 100644 --- a/logfire-api/logfire_api/_internal/integrations/psycopg.pyi +++ b/logfire-api/logfire_api/_internal/integrations/psycopg.pyi @@ -1,24 +1,17 @@ -from _typeshed import Incomplete from logfire import Logfire as Logfire from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor -from typing import Any -from typing_extensions import TypedDict, Unpack +from psycopg import AsyncConnection, Connection +from psycopg2._psycopg import connection as Psycopg2Connection +from types import ModuleType +from typing import Any, Literal +from typing_extensions import TypeVar +PsycopgConnection = TypeVar('PsycopgConnection', Connection[Any], AsyncConnection[Any], Psycopg2Connection) Instrumentor = PsycopgInstrumentor | Psycopg2Instrumentor +PACKAGE_NAMES: tuple[Literal['psycopg'], Literal['psycopg2']] -class CommenterOptions(TypedDict, total=False): - db_driver: bool - db_framework: bool - opentelemetry_values: bool - -class PsycopgInstrumentKwargs(TypedDict, total=False): - enable_commenter: bool - commenter_options: CommenterOptions - -PACKAGE_NAMES: Incomplete - -def instrument_psycopg(logfire_instance: Logfire, conn_or_module: Any = None, **kwargs: Unpack[PsycopgInstrumentKwargs]) -> None: +def instrument_psycopg(logfire_instance: Logfire, conn_or_module: ModuleType | Literal['psycopg', 'psycopg2'] | None | PsycopgConnection | Psycopg2Connection, **kwargs: Any) -> None: """Instrument a `psycopg` connection or module so that spans are automatically created for each query. See the `Logfire.instrument_psycopg` method for details. diff --git a/logfire-api/logfire_api/_internal/integrations/pymongo.pyi b/logfire-api/logfire_api/_internal/integrations/pymongo.pyi index 9dec170c3..a2646720a 100644 --- a/logfire-api/logfire_api/_internal/integrations/pymongo.pyi +++ b/logfire-api/logfire_api/_internal/integrations/pymongo.pyi @@ -1,24 +1,8 @@ -from pymongo.monitoring import CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent -from typing import Any -from typing_extensions import Protocol, TypedDict, Unpack +from opentelemetry.sdk.trace import Span as Span +from pymongo.monitoring import CommandFailedEvent as CommandFailedEvent, CommandStartedEvent as CommandStartedEvent, CommandSucceededEvent as CommandSucceededEvent +from typing import Any, Callable -class RequestHook(Protocol): - def __call__(self, span: Any, event: CommandStartedEvent) -> None: ... - -class ResponseHook(Protocol): - def __call__(self, span: Any, event: CommandSucceededEvent) -> None: ... - -class FailedHook(Protocol): - def __call__(self, span: Any, event: CommandFailedEvent) -> None: ... - -class PymongoInstrumentKwargs(TypedDict, total=False): - request_hook: RequestHook | None - response_hook: ResponseHook | None - failed_hook: FailedHook | None - capture_statement: bool | None - skip_dep_check: bool - -def instrument_pymongo(**kwargs: Unpack[PymongoInstrumentKwargs]) -> None: +def instrument_pymongo(*, capture_statement: bool, request_hook: Callable[[Span, CommandStartedEvent], None] | None = None, response_hook: Callable[[Span, CommandSucceededEvent], None] | None = None, failed_hook: Callable[[Span, CommandFailedEvent], None] | None = None, **kwargs: Any) -> None: """Instrument the `pymongo` module so that spans are automatically created for each operation. See the `Logfire.instrument_pymongo` method for details. diff --git a/logfire-api/logfire_api/_internal/integrations/redis.pyi b/logfire-api/logfire_api/_internal/integrations/redis.pyi index 02fada4d7..78f8a6f82 100644 --- a/logfire-api/logfire_api/_internal/integrations/redis.pyi +++ b/logfire-api/logfire_api/_internal/integrations/redis.pyi @@ -1,27 +1,10 @@ from logfire._internal.constants import ATTRIBUTES_MESSAGE_KEY as ATTRIBUTES_MESSAGE_KEY from logfire._internal.utils import truncate_string as truncate_string -from opentelemetry.trace import Span -from redis import Connection +from logfire.integrations.redis import RequestHook as RequestHook, ResponseHook as ResponseHook from typing import Any -from typing_extensions import Protocol, TypedDict, Unpack -class RequestHook(Protocol): - def __call__(self, span: Span, instance: Connection, *args: Any, **kwargs: Any) -> None: ... - -class ResponseHook(Protocol): - def __call__(self, span: Span, instance: Connection, response: Any) -> None: ... - -class RedisInstrumentKwargs(TypedDict, total=False): - request_hook: RequestHook | None - response_hook: ResponseHook | None - skip_dep_check: bool - -def instrument_redis(capture_statement: bool = False, **kwargs: Unpack[RedisInstrumentKwargs]) -> None: +def instrument_redis(*, capture_statement: bool, request_hook: RequestHook | None, response_hook: ResponseHook | None, **kwargs: Any) -> None: """Instrument the `redis` module so that spans are automatically created for each operation. See the `Logfire.instrument_redis` method for details. - - Args: - capture_statement: Whether to capture the statement being executed. Defaults to False. - **kwargs: Additional keyword arguments to pass to the `RedisInstrumentor.instrument` method. """ diff --git a/logfire-api/logfire_api/_internal/integrations/sqlalchemy.pyi b/logfire-api/logfire_api/_internal/integrations/sqlalchemy.pyi index 52135cd6e..92bb0f739 100644 --- a/logfire-api/logfire_api/_internal/integrations/sqlalchemy.pyi +++ b/logfire-api/logfire_api/_internal/integrations/sqlalchemy.pyi @@ -1,18 +1,9 @@ +from logfire.integrations.sqlalchemy import CommenterOptions as CommenterOptions from sqlalchemy import Engine from sqlalchemy.ext.asyncio import AsyncEngine -from typing_extensions import TypedDict, Unpack +from typing import Any -class CommenterOptions(TypedDict, total=False): - db_driver: bool - db_framework: bool - opentelemetry_values: bool - -class SQLAlchemyInstrumentKwargs(TypedDict, total=False): - enable_commenter: bool | None - commenter_options: CommenterOptions | None - skip_dep_check: bool - -def instrument_sqlalchemy(engine: AsyncEngine | Engine | None, **kwargs: Unpack[SQLAlchemyInstrumentKwargs]) -> None: +def instrument_sqlalchemy(engine: AsyncEngine | Engine | None, enable_commenter: bool, commenter_options: CommenterOptions, **kwargs: Any) -> None: """Instrument the `sqlalchemy` module so that spans are automatically created for each query. See the `Logfire.instrument_sqlalchemy` method for details. diff --git a/logfire-api/logfire_api/_internal/main.pyi b/logfire-api/logfire_api/_internal/main.pyi index 9c90169dd..956da860c 100644 --- a/logfire-api/logfire_api/_internal/main.pyi +++ b/logfire-api/logfire_api/_internal/main.pyi @@ -4,7 +4,11 @@ import openai import opentelemetry.trace as trace_api import requests from . import async_ as async_ -from ..integrations.flask import CommenterOptions as CommenterOptions, RequestHook as FlaskRequestHook, ResponseHook as FlaskResponseHook +from ..integrations.flask import CommenterOptions as FlaskCommenterOptions, RequestHook as FlaskRequestHook, ResponseHook as FlaskResponseHook +from ..integrations.httpx import AsyncRequestHook as HttpxAsyncRequestHook, AsyncResponseHook as HttpxAsyncResponseHook, RequestHook as HttpxRequestHook, ResponseHook as HttpxResponseHook +from ..integrations.psycopg import CommenterOptions as PsycopgCommenterOptions +from ..integrations.redis import RequestHook as RedisRequestHook, ResponseHook as RedisResponseHook +from ..integrations.sqlalchemy import CommenterOptions as SQLAlchemyCommenterOptions from ..integrations.wsgi import RequestHook as WSGIRequestHook, ResponseHook as WSGIResponseHook from ..version import VERSION as VERSION from .auto_trace import AutoTraceModule as AutoTraceModule, install_auto_tracing as install_auto_tracing @@ -15,12 +19,8 @@ from .formatter import logfire_format as logfire_format, logfire_format_with_mag from .instrument import instrument as instrument from .integrations.asgi import ASGIApp as ASGIApp, ASGIInstrumentKwargs as ASGIInstrumentKwargs from .integrations.aws_lambda import LambdaEvent as LambdaEvent, LambdaHandler as LambdaHandler -from .integrations.httpx import AsyncClientKwargs as AsyncClientKwargs, ClientKwargs as ClientKwargs, HTTPXInstrumentKwargs as HTTPXInstrumentKwargs -from .integrations.mysql import MySQLConnection as MySQLConnection, MySQLInstrumentKwargs as MySQLInstrumentKwargs -from .integrations.psycopg import PsycopgInstrumentKwargs as PsycopgInstrumentKwargs -from .integrations.pymongo import PymongoInstrumentKwargs as PymongoInstrumentKwargs -from .integrations.redis import RedisInstrumentKwargs as RedisInstrumentKwargs -from .integrations.sqlalchemy import SQLAlchemyInstrumentKwargs as SQLAlchemyInstrumentKwargs +from .integrations.mysql import MySQLConnection as MySQLConnection +from .integrations.psycopg import Psycopg2Connection as Psycopg2Connection, PsycopgConnection as PsycopgConnection from .integrations.sqlite3 import SQLite3Connection as SQLite3Connection from .integrations.system_metrics import Base as SystemMetricsBase, Config as SystemMetricsConfig from .json_encoder import logfire_json_dumps as logfire_json_dumps @@ -38,11 +38,13 @@ from opentelemetry.metrics import CallbackT as CallbackT, Counter, Histogram, Up from opentelemetry.sdk.trace import ReadableSpan, Span from opentelemetry.trace import SpanContext, Tracer from opentelemetry.util import types as otel_types +from pymongo.monitoring import CommandFailedEvent as CommandFailedEvent, CommandStartedEvent as CommandStartedEvent, CommandSucceededEvent as CommandSucceededEvent from sqlalchemy import Engine from sqlalchemy.ext.asyncio import AsyncEngine from starlette.applications import Starlette from starlette.requests import Request as Request from starlette.websockets import WebSocket as WebSocket +from types import ModuleType from typing import Any, Callable, ContextManager, Iterable, Literal, Sequence, TypeVar, overload from typing_extensions import LiteralString, ParamSpec, Unpack from wsgiref.types import WSGIApplication @@ -551,11 +553,11 @@ class Logfire: def instrument_asyncpg(self, **kwargs: Any) -> None: """Instrument the `asyncpg` module so that spans are automatically created for each query.""" @overload - def instrument_httpx(self, client: httpx.Client, *, capture_headers: bool = False, capture_request_text_body: bool = False, capture_request_json_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[ClientKwargs]) -> None: ... + def instrument_httpx(self, client: httpx.Client, *, capture_headers: bool = False, capture_request_body: bool = False, capture_response_body: bool = False, request_hook: HttpxRequestHook | None = None, response_hook: HttpxResponseHook | None = None, **kwargs: Any) -> None: ... @overload - def instrument_httpx(self, client: httpx.AsyncClient, *, capture_headers: bool = False, capture_request_json_body: bool = False, capture_request_text_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[AsyncClientKwargs]) -> None: ... + def instrument_httpx(self, client: httpx.AsyncClient, *, capture_headers: bool = False, capture_request_body: bool = False, capture_response_body: bool = False, request_hook: HttpxRequestHook | HttpxAsyncRequestHook | None = None, response_hook: HttpxResponseHook | HttpxAsyncResponseHook | None = None, **kwargs: Any) -> None: ... @overload - def instrument_httpx(self, client: None = None, *, capture_headers: bool = False, capture_request_json_body: bool = False, capture_request_text_body: bool = False, capture_response_json_body: bool = False, capture_request_form_data: bool = False, **kwargs: Unpack[HTTPXInstrumentKwargs]) -> None: ... + def instrument_httpx(self, client: None = None, *, capture_headers: bool = False, capture_request_body: bool = False, capture_response_body: bool = False, request_hook: HttpxRequestHook | None = None, response_hook: HttpxResponseHook | None = None, async_request_hook: HttpxAsyncRequestHook | None = None, async_response_hook: HttpxAsyncResponseHook | None = None, **kwargs: Any) -> None: ... def instrument_celery(self, **kwargs: Any) -> None: """Instrument `celery` so that spans are automatically created for each task. @@ -606,26 +608,11 @@ class Logfire: response_hook: A function called right before a span is finished for the response. **kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods, for future compatibility. """ - def instrument_psycopg(self, conn_or_module: Any = None, **kwargs: Unpack[PsycopgInstrumentKwargs]) -> None: - """Instrument a `psycopg` connection or module so that spans are automatically created for each query. - - Uses the OpenTelemetry instrumentation libraries for - [`psycopg`](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/psycopg/psycopg.html) - and - [`psycopg2`](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/psycopg2/psycopg2.html). - - Args: - conn_or_module: Can be: - - - The `psycopg` (version 3) or `psycopg2` module. - - The string `'psycopg'` or `'psycopg2'` to instrument the module. - - `None` (the default) to instrument whichever module(s) are installed. - - A `psycopg` or `psycopg2` connection. - - **kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods, - particularly `enable_commenter` and `commenter_options`. - """ - def instrument_flask(self, app: Flask, *, capture_headers: bool = False, enable_commenter: bool = True, commenter_options: CommenterOptions | None = None, exclude_urls: str | None = None, request_hook: FlaskRequestHook | None = None, response_hook: FlaskResponseHook | None = None, **kwargs: Any) -> None: + @overload + def instrument_psycopg(self, conn_or_module: PsycopgConnection | Psycopg2Connection, **kwargs: Any) -> None: ... + @overload + def instrument_psycopg(self, conn_or_module: None | Literal['psycopg', 'psycopg2'] | ModuleType = None, enable_commenter: bool = False, commenter_options: PsycopgCommenterOptions | None = None, **kwargs: Any) -> None: ... + def instrument_flask(self, app: Flask, *, capture_headers: bool = False, enable_commenter: bool = True, commenter_options: FlaskCommenterOptions | None = None, exclude_urls: str | None = None, request_hook: FlaskRequestHook | None = None, response_hook: FlaskResponseHook | None = None, **kwargs: Any) -> None: """Instrument `app` so that spans are automatically created for each request. Uses the @@ -711,7 +698,7 @@ class Logfire: [OpenTelemetry aiohttp client Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/aiohttp_client/aiohttp_client.html) library, specifically `AioHttpClientInstrumentor().instrument()`, to which it passes `**kwargs`. """ - def instrument_sqlalchemy(self, engine: AsyncEngine | Engine | None = None, **kwargs: Unpack[SQLAlchemyInstrumentKwargs]) -> None: + def instrument_sqlalchemy(self, engine: AsyncEngine | Engine | None = None, enable_commenter: bool = False, commenter_options: SQLAlchemyCommenterOptions | None = None, **kwargs: Any) -> None: """Instrument the `sqlalchemy` module so that spans are automatically created for each query. Uses the @@ -720,6 +707,8 @@ class Logfire: Args: engine: The `sqlalchemy` engine to instrument, or `None` to instrument all engines. + enable_commenter: Adds comments to SQL queries performed by SQLAlchemy, so that database logs have additional context. + commenter_options: Configure the tags to be added to the SQL comments. **kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods. """ def instrument_sqlite3(self, conn: SQLite3Connection = None, **kwargs: Any) -> SQLite3Connection: @@ -748,14 +737,21 @@ class Logfire: event_context_extractor: A function that returns an OTel Trace Context given the Lambda Event the AWS. **kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods for future compatibility. """ - def instrument_pymongo(self, **kwargs: Unpack[PymongoInstrumentKwargs]) -> None: + def instrument_pymongo(self, capture_statement: bool = False, request_hook: Callable[[Span, CommandStartedEvent], None] | None = None, response_hook: Callable[[Span, CommandSucceededEvent], None] | None = None, failed_hook: Callable[[Span, CommandFailedEvent], None] | None = None, **kwargs: Any) -> None: """Instrument the `pymongo` module so that spans are automatically created for each operation. Uses the [OpenTelemetry pymongo Instrumentation](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/pymongo/pymongo.html) library, specifically `PymongoInstrumentor().instrument()`, to which it passes `**kwargs`. + + Args: + capture_statement: Set to `True` to capture the statement in the span attributes. + request_hook: A function called when a command is sent to the server. + response_hook: A function that is called when a command is successfully completed. + failed_hook: A function that is called when a command fails. + **kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods for future compatibility. """ - def instrument_redis(self, capture_statement: bool = False, **kwargs: Unpack[RedisInstrumentKwargs]) -> None: + def instrument_redis(self, capture_statement: bool = False, request_hook: RedisRequestHook | None = None, response_hook: RedisResponseHook | None = None, **kwargs: Any) -> None: """Instrument the `redis` module so that spans are automatically created for each operation. Uses the @@ -764,9 +760,11 @@ class Logfire: Args: capture_statement: Set to `True` to capture the statement in the span attributes. - kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods. + request_hook: A function that is called before performing the request. + response_hook: A function that is called after receiving the response. + **kwargs: Additional keyword arguments to pass to the OpenTelemetry `instrument` methods for future compatibility. """ - def instrument_mysql(self, conn: MySQLConnection = None, **kwargs: Unpack[MySQLInstrumentKwargs]) -> MySQLConnection: + def instrument_mysql(self, conn: MySQLConnection = None, **kwargs: Any) -> MySQLConnection: """Instrument the `mysql` module or a specific MySQL connection so that spans are automatically created for each operation. Uses the @@ -1054,15 +1052,6 @@ class LogfireSpan(ReadableSpan): def message(self) -> str: ... @message.setter def message(self, message: str): ... - def end(self, end_time: int | None = None) -> None: - """Sets the current time as the span's end time. - - The span's end time is the wall time at which the operation finished. - - Only the first call to this method is recorded, further calls are ignored so you - can call this within the span's context manager to end it before the context manager - exits. - """ def set_attribute(self, key: str, value: Any) -> None: """Sets an attribute on the span. diff --git a/logfire-api/logfire_api/_internal/tracer.pyi b/logfire-api/logfire_api/_internal/tracer.pyi index bf8f7e75b..a3a4134b3 100644 --- a/logfire-api/logfire_api/_internal/tracer.pyi +++ b/logfire-api/logfire_api/_internal/tracer.pyi @@ -37,7 +37,13 @@ class ProxyTracerProvider(TracerProvider): @dataclass(eq=False) class _LogfireWrappedSpan(trace_api.Span, ReadableSpan): - """Span that overrides end() to use a timestamp generator if one was provided.""" + """A span that wraps another span and overrides some behaviors in a logfire-specific way. + + In particular: + * Stores a reference to itself in `OPEN_SPANS`, used to close open spans when the program exits + * Adds some logfire-specific tweaks to the exception recording behavior + * Overrides end() to use a timestamp generator if one was provided + """ span: Span ns_timestamp_generator: Callable[[], int] def __post_init__(self) -> None: ... @@ -51,6 +57,7 @@ class _LogfireWrappedSpan(trace_api.Span, ReadableSpan): def is_recording(self) -> bool: ... def set_status(self, status: Status | StatusCode, description: str | None = None) -> None: ... def record_exception(self, exception: BaseException, attributes: otel_types.Attributes = None, timestamp: int | None = None, escaped: bool = False) -> None: ... + def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: Any) -> None: ... def __getattr__(self, name: str) -> Any: ... @dataclass diff --git a/logfire-api/logfire_api/integrations/psycopg.pyi b/logfire-api/logfire_api/integrations/psycopg.pyi new file mode 100644 index 000000000..9cc4a0c34 --- /dev/null +++ b/logfire-api/logfire_api/integrations/psycopg.pyi @@ -0,0 +1,10 @@ +from typing import TypedDict + +class CommenterOptions(TypedDict, total=False): + """The `commenter_options` parameter for `instrument_psycopg`.""" + db_driver: bool + dbapi_threadsafety: bool + dbapi_level: bool + libpq_version: bool + driver_paramstyle: bool + opentelemetry_values: bool diff --git a/logfire-api/logfire_api/integrations/redis.pyi b/logfire-api/logfire_api/integrations/redis.pyi new file mode 100644 index 000000000..f28316c44 --- /dev/null +++ b/logfire-api/logfire_api/integrations/redis.pyi @@ -0,0 +1,26 @@ +from opentelemetry.trace import Span +from redis import Connection +from typing import Any, Protocol + +class RequestHook(Protocol): + """A hook that is called before the request is sent.""" + def __call__(self, span: Span, instance: Connection, *args: Any, **kwargs: Any) -> None: + """Call the hook. + + Args: + span: The span that is being created. + instance: The connection instance. + *args: The arguments that are passed to the command. + **kwargs: The keyword arguments that are passed to the command. + """ + +class ResponseHook(Protocol): + """A hook that is called after the response is received.""" + def __call__(self, span: Span, instance: Connection, response: Any) -> None: + """Call the hook. + + Args: + span: The span that is being created. + instance: The connection instance. + response: The response that is received. + """ diff --git a/logfire-api/logfire_api/integrations/sqlalchemy.pyi b/logfire-api/logfire_api/integrations/sqlalchemy.pyi new file mode 100644 index 000000000..d9d646298 --- /dev/null +++ b/logfire-api/logfire_api/integrations/sqlalchemy.pyi @@ -0,0 +1,7 @@ +from typing import TypedDict + +class CommenterOptions(TypedDict, total=False): + """The `commenter_options` parameter for `instrument_sqlalchemy`.""" + db_driver: bool + db_framework: bool + opentelemetry_values: bool diff --git a/logfire-api/logfire_api/propagate.pyi b/logfire-api/logfire_api/propagate.pyi index d005eb9b6..55ff831b7 100644 --- a/logfire-api/logfire_api/propagate.pyi +++ b/logfire-api/logfire_api/propagate.pyi @@ -1,3 +1,6 @@ +from dataclasses import dataclass +from opentelemetry import context as otel_context +from opentelemetry.propagators.textmap import TextMapPropagator from typing import Any, Iterator, Mapping __all__ = ['get_context', 'attach_context', 'ContextCarrier'] @@ -34,8 +37,38 @@ def get_context() -> ContextCarrier: ... ``` """ -def attach_context(carrier: ContextCarrier) -> Iterator[None]: +def attach_context(carrier: ContextCarrier, *, third_party: bool = False) -> Iterator[None]: """Attach a context as generated by [`get_context`][logfire.propagate.get_context] to the current execution context. Since `attach_context` is a context manager, it restores the previous context when exiting. + + Set `third_party` to `True` if using this inside a library intended to be used by others. + This will respect the [`distributed_tracing` argument of `logfire.configure()`][logfire.configure(distributed_tracing)], + so users will be warned about unintentional distributed tracing by default and they can suppress it. + See [Unintentional Distributed Tracing](https://logfire.pydantic.dev/docs/how-to-guides/distributed-tracing/#unintentional-distributed-tracing) for more information. + """ + +@dataclass +class WrapperPropagator(TextMapPropagator): + """Helper base class to wrap another propagator.""" + wrapped: TextMapPropagator + def extract(self, *args: Any, **kwargs: Any) -> otel_context.Context: ... + def inject(self, *args: Any, **kwargs: Any): ... + @property + def fields(self): ... + +class NoExtractTraceContextPropagator(WrapperPropagator): + """A propagator that ignores any trace context that was extracted by the wrapped propagator. + + Used when `logfire.configure(distributed_tracing=False)` is called. + """ + def extract(self, carrier: Any, context: otel_context.Context | None = None, *args: Any, **kwargs: Any) -> otel_context.Context: ... + +@dataclass +class WarnOnExtractTraceContextPropagator(WrapperPropagator): + """A propagator that warns the first time that trace context is extracted by the wrapped propagator. + + Used when `logfire.configure(distributed_tracing=None)` is called. This is the default behavior. """ + warned: bool = ... + def extract(self, carrier: Any, context: otel_context.Context | None = None, *args: Any, **kwargs: Any) -> otel_context.Context: ... diff --git a/logfire-api/pyproject.toml b/logfire-api/pyproject.toml index ac072bb8e..b5cdad4ff 100644 --- a/logfire-api/pyproject.toml +++ b/logfire-api/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "logfire-api" -version = "2.11.1" +version = "3.0.0" description = "Shim for the Logfire SDK which does nothing unless Logfire is installed" authors = [ { name = "Pydantic Team", email = "engineering@pydantic.dev" }, diff --git a/pyproject.toml b/pyproject.toml index c5bac1fe6..21c0fa455 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "logfire" -version = "2.11.1" +version = "3.0.0" description = "The best Python observability tool! 🪵🔥" requires-python = ">=3.8" authors = [ diff --git a/uv.lock b/uv.lock index 722474b59..07698f169 100644 --- a/uv.lock +++ b/uv.lock @@ -694,7 +694,7 @@ name = "cffi" version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, + { name = "pycparser", marker = "platform_python_implementation != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } wheels = [ @@ -867,7 +867,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "platform_system == 'Windows'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ @@ -1875,7 +1875,7 @@ wheels = [ [[package]] name = "logfire" -version = "2.11.1" +version = "3.0.0" source = { editable = "." } dependencies = [ { name = "executing" }, @@ -2171,7 +2171,7 @@ docs = [ [[package]] name = "logfire-api" -version = "2.11.1" +version = "3.0.0" source = { editable = "logfire-api" } [package.metadata] @@ -2382,7 +2382,7 @@ version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "platform_system == 'Windows'" }, { name = "ghp-import" }, { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "jinja2" }, @@ -4009,6 +4009,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 }, { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 }, { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 }, { url = "https://files.pythonhosted.org/packages/03/a7/7aa45bea9c790da0ec4765902d714ee7c43b73ccff34916261090849b715/psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", size = 3043405 }, { url = "https://files.pythonhosted.org/packages/0e/ea/e0197035d74cc1065e94f2ebf7cdd9fa4aa00bb06b1850091568345441cd/psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", size = 2851210 }, { url = "https://files.pythonhosted.org/packages/23/bf/9be0b2dd105299860e6b001ad7519e36208944609c8382d5aa2dfc58294c/psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", size = 3080972 }, @@ -5003,7 +5004,7 @@ name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "platform_system == 'Windows'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [