diff --git a/logfire/_internal/json_encoder.py b/logfire/_internal/json_encoder.py index b6c3792b0..8ed6b9529 100644 --- a/logfire/_internal/json_encoder.py +++ b/logfire/_internal/json_encoder.py @@ -282,12 +282,16 @@ def logfire_json_dumps(obj: Any) -> str: def is_sqlalchemy(obj: Any) -> bool: + if not hasattr(obj, '__mapper__'): + # A SQLModel without `table=True` will pass `isinstance(obj.__class__, DeclarativeMeta)` (I don't know how) + # but will fail when retrieving data, specifically when calling `sqlalchemy.inspect` + # or when getting the `__mapper__` attribute. + return False + try: from sqlalchemy.orm import DeclarativeBase, DeclarativeMeta - if isinstance(obj, DeclarativeBase): - return True - return isinstance(obj.__class__, DeclarativeMeta) + return isinstance(obj, DeclarativeBase) or isinstance(obj.__class__, DeclarativeMeta) except ImportError: # pragma: no cover return False diff --git a/logfire/_internal/main.py b/logfire/_internal/main.py index fbbce5dd0..2efacec65 100644 --- a/logfire/_internal/main.py +++ b/logfire/_internal/main.py @@ -176,7 +176,7 @@ def _span( if json_schema_properties := attributes_json_schema_properties(attributes): otlp_attributes[ATTRIBUTES_JSON_SCHEMA_KEY] = attributes_json_schema(json_schema_properties) - tags = cast('tuple[str, ...]', (self._tags or ()) + tuple(_tags or ())) + tags = (self._tags or ()) + tuple(_tags or ()) if tags: otlp_attributes[ATTRIBUTES_TAGS_KEY] = uniquify_sequence(tags) diff --git a/pyproject.toml b/pyproject.toml index 393cdf54d..072ced142 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,8 +132,7 @@ dev-dependencies = [ "asyncpg", "cloudpickle>=3.0.0", "anthropic>=0.27.0", - # Can remove this when https://github.com/python/typing_extensions/commit/53bcdded534494674f893112f71d3be344d65363 is released - "typing-extensions<4.12", + "sqlmodel", ] [tool.rye.scripts] @@ -191,7 +190,7 @@ quote-style = "single" typeCheckingMode = "strict" reportUnnecessaryTypeIgnoreComment = true reportMissingTypeStubs = false -exclude = ["docs/**/*.py", "site/**/*.py", ".venv", "venv*"] +exclude = ["docs/**/*.py", "site/**/*.py", ".venv", "venv*", "ignoreme"] venvPath = ".venv" [tool.pytest.ini_options] diff --git a/requirements-dev.lock b/requirements-dev.lock index 227b25966..a250c501c 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -14,7 +14,7 @@ aiosignal==1.3.1 # via aiohttp annotated-types==0.7.0 # via pydantic -anthropic==0.28.0 +anthropic==0.30.0 anyio==4.3.0 # via anthropic # via httpx @@ -55,7 +55,7 @@ cloudpickle==3.0.0 colorama==0.4.6 # via griffe # via mkdocs-material -coverage==7.5.3 +coverage==7.5.4 deprecated==1.2.14 # via opentelemetry-api # via opentelemetry-exporter-otlp-proto-http @@ -69,7 +69,7 @@ django==5.0.6 dnspython==2.6.1 # via email-validator # via pymongo -email-validator==2.1.1 +email-validator==2.2.0 # via fastapi eval-type-backport==0.2.0 executing==2.0.1 @@ -78,20 +78,20 @@ executing==2.0.1 fastapi==0.111.0 fastapi-cli==0.0.4 # via fastapi -filelock==3.14.0 +filelock==3.15.4 # via huggingface-hub # via virtualenv flask==3.0.3 frozenlist==1.4.1 # via aiohttp # via aiosignal -fsspec==2024.6.0 +fsspec==2024.6.1 # via huggingface-hub ghp-import==2.1.0 # via mkdocs -googleapis-common-protos==1.63.1 +googleapis-common-protos==1.63.2 # via opentelemetry-exporter-otlp-proto-http -griffe==0.45.2 +griffe==0.47.0 # via mkdocstrings-python h11==0.14.0 # via httpcore @@ -104,7 +104,7 @@ httpx==0.27.0 # via anthropic # via fastapi # via openai -huggingface-hub==0.23.3 +huggingface-hub==0.23.4 # via tokenizers identify==2.5.36 # via pre-commit @@ -128,7 +128,7 @@ jinja2==3.1.4 # via mkdocs # via mkdocs-material # via mkdocstrings -jiter==0.4.1 +jiter==0.5.0 # via anthropic loguru==0.7.2 markdown==3.6 @@ -159,12 +159,12 @@ mkdocs-autorefs==1.0.1 mkdocs-get-deps==0.2.0 # via mkdocs mkdocs-glightbox==0.4.0 -mkdocs-material==9.5.25 +mkdocs-material==9.5.27 mkdocs-material-extensions==1.3.1 # via mkdocs-material mkdocstrings==0.25.1 # via mkdocstrings-python -mkdocstrings-python==1.10.3 +mkdocstrings-python==1.10.5 multidict==6.0.5 # via aiohttp # via yarl @@ -173,9 +173,9 @@ mypy-extensions==1.0.0 nodeenv==1.9.1 # via pre-commit # via pyright -numpy==1.26.4 +numpy==2.0.0 # via pandas -openai==1.31.1 +openai==1.35.7 opentelemetry-api==1.25.0 # via opentelemetry-exporter-otlp-proto-http # via opentelemetry-instrumentation @@ -276,9 +276,9 @@ opentelemetry-util-http==0.46b0 # via opentelemetry-instrumentation-requests # via opentelemetry-instrumentation-starlette # via opentelemetry-instrumentation-wsgi -orjson==3.10.3 +orjson==3.10.5 # via fastapi -packaging==24.0 +packaging==24.1 # via black # via huggingface-hub # via mkdocs @@ -309,10 +309,11 @@ psycopg==3.1.19 psycopg-binary==3.1.19 # via psycopg psycopg2-binary==2.9.9 -pydantic==2.7.3 +pydantic==2.7.4 # via anthropic # via fastapi # via openai + # via sqlmodel pydantic-core==2.18.4 # via pydantic pygments==2.18.0 @@ -321,8 +322,8 @@ pygments==2.18.0 pymdown-extensions==10.8.1 # via mkdocs-material # via mkdocstrings -pymongo==4.7.3 -pyright==1.1.365 +pymongo==4.8.0 +pyright==1.1.369 pytest==8.2.2 # via pytest-django # via pytest-pretty @@ -348,7 +349,7 @@ pyyaml==6.0.1 # via uvicorn pyyaml-env-tag==0.1 # via mkdocs -redis==5.0.4 +redis==5.0.7 regex==2024.5.15 # via mkdocs-material requests==2.32.3 @@ -362,8 +363,8 @@ rich==13.7.1 # via logfire # via pytest-pretty # via typer -ruff==0.4.8 -setuptools==70.0.0 +ruff==0.5.0 +setuptools==70.1.1 # via opentelemetry-instrumentation shellingham==1.5.4 # via typer @@ -375,7 +376,9 @@ sniffio==1.3.1 # via anyio # via httpx # via openai -sqlalchemy==2.0.30 +sqlalchemy==2.0.31 + # via sqlmodel +sqlmodel==0.0.19 sqlparse==0.5.0 # via django starlette==0.37.2 @@ -392,7 +395,7 @@ typer==0.12.3 # via fastapi-cli types-toml==0.10.8.20240310 # via inline-snapshot -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via anthropic # via fastapi # via huggingface-hub @@ -408,13 +411,13 @@ tzdata==2024.1 # via pandas ujson==5.10.0 # via fastapi -urllib3==2.2.1 +urllib3==2.2.2 # via requests uvicorn==0.30.1 # via fastapi uvloop==0.19.0 # via uvicorn -virtualenv==20.26.2 +virtualenv==20.26.3 # via pre-commit watchdog==4.0.1 # via mkdocs diff --git a/requirements.lock b/requirements.lock index cf843d6ff..443d44bfb 100644 --- a/requirements.lock +++ b/requirements.lock @@ -18,7 +18,7 @@ deprecated==1.2.14 # via opentelemetry-exporter-otlp-proto-http executing==2.0.1 # via logfire -googleapis-common-protos==1.63.1 +googleapis-common-protos==1.63.2 # via opentelemetry-exporter-otlp-proto-http idna==3.7 # via requests @@ -57,12 +57,12 @@ requests==2.32.3 # via opentelemetry-exporter-otlp-proto-http rich==13.7.1 # via logfire -setuptools==70.0.0 +setuptools==70.1.1 # via opentelemetry-instrumentation -typing-extensions==4.11.0 +typing-extensions==4.12.2 # via logfire # via opentelemetry-sdk -urllib3==2.2.1 +urllib3==2.2.2 # via requests wrapt==1.16.0 # via deprecated diff --git a/tests/test_json_args.py b/tests/test_json_args.py index 9e992937b..360425c2d 100644 --- a/tests/test_json_args.py +++ b/tests/test_json_args.py @@ -26,6 +26,7 @@ from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, relationship from sqlalchemy.orm.decl_api import MappedAsDataclass from sqlalchemy.sql.schema import ForeignKey +from sqlmodel import SQLModel import logfire from logfire.testing import TestExporter @@ -70,6 +71,10 @@ class MyPydanticComplexDataclass: t: MyPydanticDataclass +class MySQLModel(SQLModel): + s: int + + class Generator: def __repr__(self) -> str: return 'Generator()' @@ -513,6 +518,12 @@ class StrSubclass(str): }, id='pydantic_model_with_extra', ), + pytest.param( + MySQLModel(s=10), + 's=10', + '{"s":10}', + {'type': 'object', 'title': 'MySQLModel', 'x-python-datatype': 'PydanticModel'}, + ), pytest.param( MyDataclass(10), 'MyDataclass(t=10)',