From a0f31293f7cdfc1886478a7131c0b08cf2f48a24 Mon Sep 17 00:00:00 2001 From: doronkopit5 Date: Mon, 24 Nov 2025 22:20:44 +0200 Subject: [PATCH 1/3] feat(traceloop-sdk): add type-checking support with mypy and update dependencies - Added mypy for static type checking in the traceloop-sdk. - Updated poetry.lock to include mypy and its dependencies. - Enhanced CI workflow to include type-checking step. - Refactored user feedback and dataset classes to use entity_instance_id. - Improved type annotations across various modules for better type safety. --- .github/workflows/ci.yml | 1 + packages/traceloop-sdk/poetry.lock | 212 +++++++++++++++--- packages/traceloop-sdk/project.json | 8 + packages/traceloop-sdk/pyproject.toml | 50 +++++ .../traceloop-sdk/tests/test_user_feedback.py | 18 +- .../sdk/annotation/base_annotation.py | 8 +- .../traceloop/sdk/annotation/user_feedback.py | 64 +++--- .../traceloop/sdk/client/client.py | 3 +- .../traceloop/sdk/client/http.py | 2 +- .../traceloop/sdk/dataset/dataset.py | 4 +- .../traceloop/sdk/evaluator/stream_client.py | 8 +- .../traceloop/sdk/experiment/experiment.py | 19 +- packages/traceloop-sdk/traceloop/sdk/py.typed | 0 13 files changed, 303 insertions(+), 94 deletions(-) create mode 100644 packages/traceloop-sdk/traceloop/sdk/py.typed diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdcbb55ac1..421ac6d58f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,6 +74,7 @@ jobs: run: npm cache clean --force || true - run: npx nx affected -t install --with dev - run: npx nx affected -t lint --parallel=3 + - run: npx nx affected -t type-check --parallel=3 build-packages: name: Build Packages diff --git a/packages/traceloop-sdk/poetry.lock b/packages/traceloop-sdk/poetry.lock index 88d2b8eb7e..065577942d 100644 --- a/packages/traceloop-sdk/poetry.lock +++ b/packages/traceloop-sdk/poetry.lock @@ -1419,6 +1419,81 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} +[[package]] +name = "mypy" +version = "1.18.2" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + [[package]] name = "numpy" version = "1.26.4" @@ -1594,7 +1669,7 @@ wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-instrumentation-agno" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Agno instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1618,7 +1693,7 @@ url = "../opentelemetry-instrumentation-agno" [[package]] name = "opentelemetry-instrumentation-alephalpha" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Aleph Alpha instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1642,7 +1717,7 @@ url = "../opentelemetry-instrumentation-alephalpha" [[package]] name = "opentelemetry-instrumentation-anthropic" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Anthropic instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1666,7 +1741,7 @@ url = "../opentelemetry-instrumentation-anthropic" [[package]] name = "opentelemetry-instrumentation-bedrock" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Bedrock instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1689,7 +1764,7 @@ url = "../opentelemetry-instrumentation-bedrock" [[package]] name = "opentelemetry-instrumentation-chromadb" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Chroma DB instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1713,7 +1788,7 @@ url = "../opentelemetry-instrumentation-chromadb" [[package]] name = "opentelemetry-instrumentation-cohere" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Cohere instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1737,7 +1812,7 @@ url = "../opentelemetry-instrumentation-cohere" [[package]] name = "opentelemetry-instrumentation-crewai" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry crewAI instrumentation" optional = false python-versions = ">=3.10,<4" @@ -1761,7 +1836,7 @@ url = "../opentelemetry-instrumentation-crewai" [[package]] name = "opentelemetry-instrumentation-google-generativeai" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Google Generative AI instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1785,7 +1860,7 @@ url = "../opentelemetry-instrumentation-google-generativeai" [[package]] name = "opentelemetry-instrumentation-groq" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Groq instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1809,7 +1884,7 @@ url = "../opentelemetry-instrumentation-groq" [[package]] name = "opentelemetry-instrumentation-haystack" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Haystack instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1833,7 +1908,7 @@ url = "../opentelemetry-instrumentation-haystack" [[package]] name = "opentelemetry-instrumentation-lancedb" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Lancedb instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1857,7 +1932,7 @@ url = "../opentelemetry-instrumentation-lancedb" [[package]] name = "opentelemetry-instrumentation-langchain" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Langchain instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1881,7 +1956,7 @@ url = "../opentelemetry-instrumentation-langchain" [[package]] name = "opentelemetry-instrumentation-llamaindex" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry LlamaIndex instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1924,7 +1999,7 @@ opentelemetry-instrumentation = "0.59b0" [[package]] name = "opentelemetry-instrumentation-marqo" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Marqo instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1948,7 +2023,7 @@ url = "../opentelemetry-instrumentation-marqo" [[package]] name = "opentelemetry-instrumentation-mcp" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry mcp instrumentation" optional = false python-versions = ">=3.10,<4" @@ -1972,7 +2047,7 @@ url = "../opentelemetry-instrumentation-mcp" [[package]] name = "opentelemetry-instrumentation-milvus" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Milvus instrumentation" optional = false python-versions = ">=3.9,<4" @@ -1996,7 +2071,7 @@ url = "../opentelemetry-instrumentation-milvus" [[package]] name = "opentelemetry-instrumentation-mistralai" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Mistral AI instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2020,7 +2095,7 @@ url = "../opentelemetry-instrumentation-mistralai" [[package]] name = "opentelemetry-instrumentation-ollama" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Ollama instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2044,7 +2119,7 @@ url = "../opentelemetry-instrumentation-ollama" [[package]] name = "opentelemetry-instrumentation-openai" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry OpenAI instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2068,7 +2143,7 @@ url = "../opentelemetry-instrumentation-openai" [[package]] name = "opentelemetry-instrumentation-openai-agents" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry OpenAI Agents instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2092,7 +2167,7 @@ url = "../opentelemetry-instrumentation-openai-agents" [[package]] name = "opentelemetry-instrumentation-pinecone" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Pinecone instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2116,7 +2191,7 @@ url = "../opentelemetry-instrumentation-pinecone" [[package]] name = "opentelemetry-instrumentation-qdrant" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Qdrant instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2162,7 +2237,7 @@ instruments = ["redis (>=2.6)"] [[package]] name = "opentelemetry-instrumentation-replicate" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Replicate instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2208,7 +2283,7 @@ instruments = ["requests (>=2.0,<3.0)"] [[package]] name = "opentelemetry-instrumentation-sagemaker" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry SageMaker instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2270,7 +2345,7 @@ wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-instrumentation-together" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Together AI instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2294,7 +2369,7 @@ url = "../opentelemetry-instrumentation-together" [[package]] name = "opentelemetry-instrumentation-transformers" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry transformers instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2338,7 +2413,7 @@ instruments = ["urllib3 (>=1.0.0,<3.0.0)"] [[package]] name = "opentelemetry-instrumentation-vertexai" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Vertex AI instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2362,7 +2437,7 @@ url = "../opentelemetry-instrumentation-vertexai" [[package]] name = "opentelemetry-instrumentation-watsonx" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry IBM Watsonx Instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2386,7 +2461,7 @@ url = "../opentelemetry-instrumentation-watsonx" [[package]] name = "opentelemetry-instrumentation-weaviate" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Weaviate instrumentation" optional = false python-versions = ">=3.9,<4" @@ -2410,7 +2485,7 @@ url = "../opentelemetry-instrumentation-weaviate" [[package]] name = "opentelemetry-instrumentation-writer" -version = "0.48.2" +version = "0.49.1" description = "OpenTelemetry Writer instrumentation" optional = false python-versions = ">=3.10,<4" @@ -2696,6 +2771,19 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + [[package]] name = "pluggy" version = "1.5.0" @@ -3644,13 +3732,71 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] +[[package]] +name = "types-colorama" +version = "0.4.15.20250801" +description = "Typing stubs for colorama" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "types_colorama-0.4.15.20250801-py3-none-any.whl", hash = "sha256:b6e89bd3b250fdad13a8b6a465c933f4a5afe485ea2e2f104d739be50b13eea9"}, + {file = "types_colorama-0.4.15.20250801.tar.gz", hash = "sha256:02565d13d68963d12237d3f330f5ecd622a3179f7b5b14ee7f16146270c357f5"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.6" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +markers = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, + {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, +] + +[package.dependencies] +types-urllib3 = "*" + +[[package]] +name = "types-requests" +version = "2.32.4.20250913" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1"}, + {file = "types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "types-urllib3" +version = "1.26.25.14" +description = "Typing stubs for urllib3" +optional = false +python-versions = "*" +groups = ["dev"] +markers = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, + {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "platform_python_implementation == \"PyPy\"" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, @@ -3694,7 +3840,7 @@ version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "test"] +groups = ["main", "dev", "test"] markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, @@ -3947,4 +4093,4 @@ datasets = [] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4" -content-hash = "e52b485dcebf99a88beebec4e8db514ec8d324408aa9b5af84c47c4c2ea2a089" +content-hash = "7d839e4336cf955e5739e277e6e80f59a8defdf699059d3b00a83bf103bc58b2" diff --git a/packages/traceloop-sdk/project.json b/packages/traceloop-sdk/project.json index f9c3c9753f..a6a7d227e7 100644 --- a/packages/traceloop-sdk/project.json +++ b/packages/traceloop-sdk/project.json @@ -50,6 +50,14 @@ "outputFile": "reports/packages/traceloop-sdk/pylint.txt" } }, + "type-check": { + "executor": "@nxlv/python:run-commands", + "outputs": [], + "options": { + "command": "poetry run mypy", + "cwd": "packages/traceloop-sdk" + } + }, "test": { "executor": "@nxlv/python:run-commands", "outputs": [ diff --git a/packages/traceloop-sdk/pyproject.toml b/packages/traceloop-sdk/pyproject.toml index 4903011a02..c13125b14f 100644 --- a/packages/traceloop-sdk/pyproject.toml +++ b/packages/traceloop-sdk/pyproject.toml @@ -80,6 +80,9 @@ autopep8 = "^2.2.0" flake8 = "7.0.0" pytest = "^8.2.2" pytest-sugar = "1.0.0" +mypy = "^1.18.2" +types-requests = "^2.31.0" +types-colorama = "^0.4.15" [tool.poetry.group.test.dependencies] openai = "^1.31.1" @@ -95,6 +98,53 @@ pandas = ">=1.0.0" [tool.poetry.extras] datasets = ["pandas"] +[tool.mypy] +python_version = "3.10" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_any_unimported = false +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +check_untyped_defs = true +strict_equality = true +namespace_packages = true +explicit_package_bases = true +plugins = ["pydantic.mypy"] + +# Target specific folders for strict type checking +files = [ + "traceloop/sdk/annotation", + "traceloop/sdk/client", + "traceloop/sdk/dataset", + "traceloop/sdk/evaluator", + "traceloop/sdk/experiment", +] + +# Exclude other folders (blacklist approach - new folders will be checked by default) +exclude = [ + "traceloop/sdk/decorators", + "traceloop/sdk/prompts", + "traceloop/sdk/tracing", + "traceloop/sdk/utils", + "traceloop/sdk/__init__.py", + "tests/", +] + +[[tool.mypy.overrides]] +module = [ + "cuid.*", + "posthog.*", +] +ignore_missing_imports = true + +[pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/packages/traceloop-sdk/tests/test_user_feedback.py b/packages/traceloop-sdk/tests/test_user_feedback.py index c73a7f8acc..c15bf4b62f 100644 --- a/packages/traceloop-sdk/tests/test_user_feedback.py +++ b/packages/traceloop-sdk/tests/test_user_feedback.py @@ -35,10 +35,10 @@ def test_user_feedback_initialization(mock_http): assert feedback._app_name == "test-app" -def test_create_basic_feedback(user_feedback, mock_http): +def test_create_basic_feedback(user_feedback: UserFeedback, mock_http: Mock): """Test creating basic user feedback""" user_feedback.create( - annotation_task="task_123", entity_id="instance_456", tags={"sentiment": "positive"} + annotation_task="task_123", entity_instance_id="instance_456", tags={"sentiment": "positive"} ) mock_http.post.assert_called_once_with( @@ -56,11 +56,11 @@ def test_create_basic_feedback(user_feedback, mock_http): ) -def test_create_feedback_complex_tags(user_feedback, mock_http): +def test_create_feedback_complex_tags(user_feedback: UserFeedback, mock_http: Mock): """Test creating user feedback with complex tags""" tags = {"sentiment": "positive", "relevance": 0.95, "tones": ["happy", "nice"]} - user_feedback.create(annotation_task="task_123", entity_id="instance_456", tags=tags) + user_feedback.create(annotation_task="task_123", entity_instance_id="instance_456", tags=tags) mock_http.post.assert_called_once_with( "annotation-tasks/task_123/annotations", @@ -77,13 +77,13 @@ def test_create_feedback_complex_tags(user_feedback, mock_http): ) -def test_create_feedback_parameter_validation(user_feedback): +def test_create_feedback_parameter_validation(user_feedback: UserFeedback): """Test parameter validation for feedback creation""" with pytest.raises(ValueError, match="annotation_task is required"): - user_feedback.create(annotation_task="", entity_id="instance_456", tags={"sentiment": "positive"}) + user_feedback.create(annotation_task="", entity_instance_id="instance_456", tags={"sentiment": "positive"}) - with pytest.raises(ValueError, match="entity_id is required"): - user_feedback.create(annotation_task="task_123", entity_id="", tags={"sentiment": "positive"}) + with pytest.raises(ValueError, match="entity_instance_id is required"): + user_feedback.create(annotation_task="task_123", entity_instance_id="", tags={"sentiment": "positive"}) with pytest.raises(ValueError, match="tags cannot be empty"): - user_feedback.create(annotation_task="task_123", entity_id="instance_456", tags={}) + user_feedback.create(annotation_task="task_123", entity_instance_id="instance_456", tags={}) diff --git a/packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py b/packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py index 34d273540e..44d1cb838d 100644 --- a/packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py +++ b/packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py @@ -21,7 +21,7 @@ def __init__(self, http: HTTPClient, app_name: str, flow: str): def create( self, annotation_task: str, - entity_id: str, + entity_instance_id: str, tags: Dict[str, Any], ) -> None: """Create an user feedback annotation for a specific task. @@ -51,15 +51,15 @@ def create( if not annotation_task: raise ValueError("annotation_task is required") - if not entity_id: - raise ValueError("entity_id is required") + if not entity_instance_id: + raise ValueError("entity_instance_id is required") if not tags: raise ValueError("tags cannot be empty") self._http.post( f"annotation-tasks/{annotation_task}/annotations", { - "entity_instance_id": entity_id, + "entity_instance_id": entity_instance_id, "tags": tags, "source": "sdk", "flow": self._flow, diff --git a/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py b/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py index b0ab645e00..cb119f37cd 100644 --- a/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py +++ b/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py @@ -9,35 +9,35 @@ def __init__(self, http: HTTPClient, app_name: str): super().__init__(http, app_name, "user_feedback") -def create( - self, - annotation_task: str, - entity_instance_id: str, - tags: Dict[str, Any], -) -> None: - """Create an annotation for a specific task. - - Args: - annotation_task (str): The ID/slug of the annotation task to report to. - Can be found at app.traceloop.com/annotation_tasks/:annotation_task_id - entity_instance_id (str): The ID of the specific entity instance being annotated, should be reported - in the association properties - tags (Dict[str, Any]): Dictionary containing the tags to be reported. - Should match the tags defined in the annotation task - - Example: - ```python - client = Client(api_key="your-key") - client.annotation.create( - annotation_task="task_123", - entity_instance_id="instance_456", - tags={ - "sentiment": "positive", - "relevance": 0.95, - "tones": ["happy", "nice"] - }, - ) - ``` - """ - - return BaseAnnotation.create(self, annotation_task, entity_instance_id, tags) + def create( + self, + annotation_task: str, + entity_instance_id: str, + tags: Dict[str, Any], + ) -> None: + """Create an annotation for a specific task. + + Args: + annotation_task (str): The ID/slug of the annotation task to report to. + Can be found at app.traceloop.com/annotation_tasks/:annotation_task_id + entity_instance_id (str): The ID of the specific entity instance being annotated, should be reported + in the association properties + tags (Dict[str, Any]): Dictionary containing the tags to be reported. + Should match the tags defined in the annotation task + + Example: + ```python + client = Client(api_key="your-key") + client.annotation.create( + annotation_task="task_123", + entity_instance_id="instance_456", + tags={ + "sentiment": "positive", + "relevance": 0.95, + "tones": ["happy", "nice"] + }, + ) + ``` + """ + + return BaseAnnotation.create(self, annotation_task, entity_instance_id, tags) diff --git a/packages/traceloop-sdk/traceloop/sdk/client/client.py b/packages/traceloop-sdk/traceloop/sdk/client/client.py index a1ebc8b2e5..b3d2510f63 100644 --- a/packages/traceloop-sdk/traceloop/sdk/client/client.py +++ b/packages/traceloop-sdk/traceloop/sdk/client/client.py @@ -63,4 +63,5 @@ def __init__( self.user_feedback = UserFeedback(self._http, self.app_name) self.datasets = Datasets(self._http) experiment_slug = os.getenv("TRACELOOP_EXP_SLUG") - self.experiment = Experiment(self._http, self._async_http, experiment_slug) + # TODO: Fix type - Experiment constructor should accept Optional[str] + self.experiment = Experiment(self._http, self._async_http, experiment_slug) # type: ignore[arg-type] diff --git a/packages/traceloop-sdk/traceloop/sdk/client/http.py b/packages/traceloop-sdk/traceloop/sdk/client/http.py index 60287bf593..1e1af895f1 100644 --- a/packages/traceloop-sdk/traceloop/sdk/client/http.py +++ b/packages/traceloop-sdk/traceloop/sdk/client/http.py @@ -14,7 +14,7 @@ def __init__(self, base_url: str, api_key: str, version: str): self.api_key = api_key self.version = version - def _headers(self): + def _headers(self) -> Dict[str, str]: return { "Authorization": f"Bearer {self.api_key}", "X-Traceloop-SDK-Version": self.version, diff --git a/packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py b/packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py index 6f3c6a804e..1506ff0aaf 100644 --- a/packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py +++ b/packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py @@ -86,7 +86,7 @@ def add_column(self, slug: str, name: str, col_type: ColumnType) -> Column: self.columns.append(column) return column - def _create_columns(self, raw_columns: Dict[str, ColumnDefinition]): + def _create_columns(self, raw_columns: Dict[str, ColumnDefinition]) -> None: """Create Column objects from API response which includes column IDs""" for column_slug, column_def in raw_columns.items(): column = Column( @@ -98,7 +98,7 @@ def _create_columns(self, raw_columns: Dict[str, ColumnDefinition]): ) self.columns.append(column) - def _create_rows(self, raw_rows: List[RowObject]): + def _create_rows(self, raw_rows: List[RowObject]) -> None: for _, row_obj in enumerate(raw_rows): row = Row( http=self._http, diff --git a/packages/traceloop-sdk/traceloop/sdk/evaluator/stream_client.py b/packages/traceloop-sdk/traceloop/sdk/evaluator/stream_client.py index 2dfb8e37bf..af933a1563 100644 --- a/packages/traceloop-sdk/traceloop/sdk/evaluator/stream_client.py +++ b/packages/traceloop-sdk/traceloop/sdk/evaluator/stream_client.py @@ -52,13 +52,13 @@ async def wait_for_result( except Exception as e: raise Exception(f"Unexpected error in SSE stream: {e}") - async def _handle_sse_response(self, response) -> ExecutionResponse: + async def _handle_sse_response(self, response: httpx.Response) -> ExecutionResponse: """Handle SSE response: check status and parse result""" if response.status_code != 200: error_text = await response.aread() - raise Exception( - f"Failed to stream results: {response.status_code}, body: {error_text}" - ) + # TODO: Fix bytes formatting - should decode error_text or use !r + error_msg = f"Failed to stream results: {response.status_code}, body: {error_text}" # type: ignore[str-bytes-safe] # noqa: E501 + raise Exception(error_msg) response_text = await response.aread() return self._parse_sse_result(response_text.decode()) diff --git a/packages/traceloop-sdk/traceloop/sdk/experiment/experiment.py b/packages/traceloop-sdk/traceloop/sdk/experiment/experiment.py index 6f0baf70d3..9041d8ebe3 100644 --- a/packages/traceloop-sdk/traceloop/sdk/experiment/experiment.py +++ b/packages/traceloop-sdk/traceloop/sdk/experiment/experiment.py @@ -95,13 +95,15 @@ async def run( results: List[TaskResponse] = [] errors: List[str] = [] - async def run_single_row(row) -> TaskResponse: + async def run_single_row(row: Optional[Dict[str, Any]]) -> TaskResponse: try: - task_result = await task(row) + # TODO: Fix type annotation - task should return Awaitable, not dict + task_result = await task(row) # type: ignore[misc] + # TODO: Fix type - task_input should accept Optional[Dict] task_id = self._create_task( experiment_slug=experiment_slug, experiment_run_id=run_id, - task_input=row, + task_input=row, # type: ignore[arg-type] task_output=task_result, ).id @@ -132,12 +134,13 @@ async def run_single_row(row) -> TaskResponse: input=task_result, ) - eval_results[evaluator_slug] = ( - f"Triggered execution of {evaluator_slug}" - ) + # TODO: Fix type - eval_results should accept Union[Dict, str] + msg = f"Triggered execution of {evaluator_slug}" + eval_results[evaluator_slug] = msg # type: ignore[assignment] except Exception as e: - eval_results[evaluator_slug] = f"Error: {str(e)}" + # TODO: Fix type - eval_results should accept Union[Dict, str] + eval_results[evaluator_slug] = f"Error: {str(e)}" # type: ignore[assignment] return TaskResponse( task_result=task_result, @@ -151,7 +154,7 @@ async def run_single_row(row) -> TaskResponse: semaphore = asyncio.Semaphore(50) - async def run_with_semaphore(row) -> TaskResponse: + async def run_with_semaphore(row: Optional[Dict[str, Any]]) -> TaskResponse: async with semaphore: return await run_single_row(row) diff --git a/packages/traceloop-sdk/traceloop/sdk/py.typed b/packages/traceloop-sdk/traceloop/sdk/py.typed new file mode 100644 index 0000000000..e69de29bb2 From c6cb82f87e5b64965675daa22178eb797e7182f3 Mon Sep 17 00:00:00 2001 From: doronkopit5 Date: Mon, 24 Nov 2025 23:41:30 +0200 Subject: [PATCH 2/3] feat(traceloop-sdk): enhance type annotations and add new dependencies - Updated poetry.lock to include pandas-stubs and types-pytz for improved type checking. - Modified project.json to specify the mypy command for type checking the sdk directory. - Enhanced type annotations across various modules for better type safety and clarity. - Refactored function signatures to include return types for improved type hinting. --- packages/traceloop-sdk/poetry.lock | 34 +++++++++++++++++-- packages/traceloop-sdk/project.json | 2 +- packages/traceloop-sdk/pyproject.toml | 12 ++----- .../traceloop/sdk/annotation/user_feedback.py | 1 - .../traceloop/sdk/datasets/datasets.py | 13 ++++--- .../traceloop/sdk/decorators/__init__.py | 6 ++-- .../traceloop/sdk/decorators/base.py | 7 +--- .../traceloop-sdk/traceloop/sdk/fetcher.py | 20 +++++------ .../traceloop/sdk/images/image_uploader.py | 18 ++++++---- .../traceloop/sdk/logging/logging.py | 14 ++++---- .../traceloop/sdk/metrics/metrics.py | 14 ++++---- .../traceloop-sdk/traceloop/sdk/telemetry.py | 32 +++++++++-------- 12 files changed, 100 insertions(+), 73 deletions(-) diff --git a/packages/traceloop-sdk/poetry.lock b/packages/traceloop-sdk/poetry.lock index 065577942d..05a0513e41 100644 --- a/packages/traceloop-sdk/poetry.lock +++ b/packages/traceloop-sdk/poetry.lock @@ -1500,7 +1500,7 @@ version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.9" -groups = ["test"] +groups = ["dev", "test"] markers = "platform_python_implementation == \"PyPy\"" files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, @@ -2771,6 +2771,23 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] +[[package]] +name = "pandas-stubs" +version = "2.3.2.250926" +description = "Type annotations for pandas" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +markers = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "pandas_stubs-2.3.2.250926-py3-none-any.whl", hash = "sha256:81121818453dcfe00f45c852f4dceee043640b813830f6e7bd084a4ef7ff7270"}, + {file = "pandas_stubs-2.3.2.250926.tar.gz", hash = "sha256:c64b9932760ceefb96a3222b953e6a251321a9832a28548be6506df473a66406"}, +] + +[package.dependencies] +numpy = ">=1.23.5" +types-pytz = ">=2022.1.1" + [[package]] name = "pathspec" version = "0.12.1" @@ -3745,6 +3762,19 @@ files = [ {file = "types_colorama-0.4.15.20250801.tar.gz", hash = "sha256:02565d13d68963d12237d3f330f5ecd622a3179f7b5b14ee7f16146270c357f5"}, ] +[[package]] +name = "types-pytz" +version = "2025.2.0.20251108" +description = "Typing stubs for pytz" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation == \"PyPy\"" +files = [ + {file = "types_pytz-2025.2.0.20251108-py3-none-any.whl", hash = "sha256:0f1c9792cab4eb0e46c52f8845c8f77cf1e313cb3d68bf826aa867fe4717d91c"}, + {file = "types_pytz-2025.2.0.20251108.tar.gz", hash = "sha256:fca87917836ae843f07129567b74c1929f1870610681b4c92cb86a3df5817bdb"}, +] + [[package]] name = "types-requests" version = "2.31.0.6" @@ -4093,4 +4123,4 @@ datasets = [] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4" -content-hash = "7d839e4336cf955e5739e277e6e80f59a8defdf699059d3b00a83bf103bc58b2" +content-hash = "1c830029d991806c6e1faf0aec609040f5bea3a6b8bd2f7dbe104ac630e9eebc" diff --git a/packages/traceloop-sdk/project.json b/packages/traceloop-sdk/project.json index a6a7d227e7..2fbf34b1b1 100644 --- a/packages/traceloop-sdk/project.json +++ b/packages/traceloop-sdk/project.json @@ -54,7 +54,7 @@ "executor": "@nxlv/python:run-commands", "outputs": [], "options": { - "command": "poetry run mypy", + "command": "poetry run mypy traceloop/sdk", "cwd": "packages/traceloop-sdk" } }, diff --git a/packages/traceloop-sdk/pyproject.toml b/packages/traceloop-sdk/pyproject.toml index c13125b14f..ddc2f46e93 100644 --- a/packages/traceloop-sdk/pyproject.toml +++ b/packages/traceloop-sdk/pyproject.toml @@ -83,6 +83,7 @@ pytest-sugar = "1.0.0" mypy = "^1.18.2" types-requests = "^2.31.0" types-colorama = "^0.4.15" +pandas-stubs = "*" [tool.poetry.group.test.dependencies] openai = "^1.31.1" @@ -114,16 +115,7 @@ namespace_packages = true explicit_package_bases = true plugins = ["pydantic.mypy"] -# Target specific folders for strict type checking -files = [ - "traceloop/sdk/annotation", - "traceloop/sdk/client", - "traceloop/sdk/dataset", - "traceloop/sdk/evaluator", - "traceloop/sdk/experiment", -] - -# Exclude other folders (blacklist approach - new folders will be checked by default) +# Blacklist approach - all folders checked except those excluded below exclude = [ "traceloop/sdk/decorators", "traceloop/sdk/prompts", diff --git a/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py b/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py index cb119f37cd..fce3f2790c 100644 --- a/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py +++ b/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py @@ -8,7 +8,6 @@ class UserFeedback(BaseAnnotation): def __init__(self, http: HTTPClient, app_name: str): super().__init__(http, app_name, "user_feedback") - def create( self, annotation_task: str, diff --git a/packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py b/packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py index ded12fae2b..1d3d9ae87e 100644 --- a/packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py +++ b/packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py @@ -1,5 +1,5 @@ import csv -from typing import List, Optional +from typing import List, Optional, cast from pathlib import Path try: @@ -81,6 +81,10 @@ def from_csv( reader = csv.DictReader(csvfile, delimiter=delimiter) + # TODO: Handle None case for fieldnames more gracefully + if reader.fieldnames is None: + raise ValueError("CSV file has no headers") + for field_name in reader.fieldnames: columns_definition.append( ColumnDefinition( @@ -138,8 +142,9 @@ def from_dataframe( ) ) + # TODO: Pandas returns Hashable keys, should ensure they're strings rows = [ - {self._slugify(k): v for k, v in row.items()} + {self._slugify(str(k)): v for k, v in row.items()} for row in df.to_dict(orient="records") ] @@ -160,14 +165,14 @@ def get_version_csv(self, slug: str, version: str) -> str: result = self._http.get(f"datasets/{slug}/versions/{version}") if result is None: raise Exception(f"Failed to get dataset {slug} by version {version}") - return result + return cast(str, result) def get_version_jsonl(self, slug: str, version: str) -> str: """Get a specific version of a dataset as a JSONL string""" result = self._http.get(f"datasets/{slug}/versions/{version}/jsonl") if result is None: raise Exception(f"Failed to get dataset {slug} by version {version}") - return result + return cast(str, result) def _create_dataset(self, input: CreateDatasetRequest) -> CreateDatasetResponse: """Create new dataset""" diff --git a/packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py b/packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py index 2a7a216bcd..5af2ffe8e5 100644 --- a/packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py +++ b/packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py @@ -1,4 +1,4 @@ -from typing import Optional, TypeVar, Callable, Any, ParamSpec, Awaitable +from typing import Optional, TypeVar, Callable import warnings from opentelemetry.semconv_ai import TraceloopSpanKindValues @@ -8,9 +8,7 @@ entity_method, ) -P = ParamSpec("P") -R = TypeVar("R") -F = TypeVar("F", bound=Callable[P, R | Awaitable[R]]) +F = TypeVar("F") def task( diff --git a/packages/traceloop-sdk/traceloop/sdk/decorators/base.py b/packages/traceloop-sdk/traceloop/sdk/decorators/base.py index ab7f401e43..2cdcfe633e 100644 --- a/packages/traceloop-sdk/traceloop/sdk/decorators/base.py +++ b/packages/traceloop-sdk/traceloop/sdk/decorators/base.py @@ -7,8 +7,6 @@ Callable, Any, cast, - ParamSpec, - Awaitable, ) import inspect import warnings @@ -29,10 +27,7 @@ from traceloop.sdk.utils import camel_to_snake from traceloop.sdk.utils.json_encoder import JSONEncoder -P = ParamSpec("P") - -R = TypeVar("R") -F = TypeVar("F", bound=Callable[P, R | Awaitable[R]]) +F = TypeVar("F") def _truncate_json_if_needed(json_str: str) -> str: diff --git a/packages/traceloop-sdk/traceloop/sdk/fetcher.py b/packages/traceloop-sdk/traceloop/sdk/fetcher.py index 12ac308119..0abc413e5a 100644 --- a/packages/traceloop-sdk/traceloop/sdk/fetcher.py +++ b/packages/traceloop-sdk/traceloop/sdk/fetcher.py @@ -6,7 +6,7 @@ import requests from threading import Thread, Event -from typing import Dict +from typing import Dict, Any from tenacity import ( RetryError, retry, @@ -51,7 +51,7 @@ def __init__(self, base_url: str, api_key: str): ), ) - def run(self): + def run(self) -> None: refresh_data( self._base_url, self._api_key, @@ -61,10 +61,10 @@ def run(self): self._exit_monitor.start() self._poller_thread.start() - def post(self, api: str, body: Dict[str, str]): + def post(self, api: str, body: Dict[str, str]) -> None: post_url(f"{self._base_url}/v1/traceloop/{api}", self._api_key, body) - def api_post(self, api: str, body: Dict[str, typing.Any]): + def api_post(self, api: str, body: Dict[str, typing.Any]) -> None: try: post_url(f"{self._base_url}/v2/{api}", self._api_key, body) except Exception as e: @@ -83,7 +83,7 @@ def __init__( super().__init__(lambda e: check_http_error(e)) -def check_http_error(e): +def check_http_error(e: BaseException) -> bool: return isinstance(e, requests.exceptions.HTTPError) and ( 500 <= e.response.status_code < 600 ) @@ -94,7 +94,7 @@ def check_http_error(e): stop=stop_after_attempt(MAX_RETRIES), retry=RetryIfServerError(), ) -def fetch_url(url: str, api_key: str): +def fetch_url(url: str, api_key: str) -> Any: response = requests.get( url, headers={ @@ -114,7 +114,7 @@ def fetch_url(url: str, api_key: str): return response.json() -def post_url(url: str, api_key: str, body: Dict[str, typing.Any]): +def post_url(url: str, api_key: str, body: Dict[str, typing.Any]) -> None: response = requests.post( url, headers={ @@ -135,7 +135,7 @@ def thread_func( api_key: str, stop_polling_event: Event, seconds_interval: float = 5.0, -): +) -> None: while not stop_polling_event.is_set(): try: refresh_data(base_url, api_key, prompt_registry, content_allow_list) @@ -151,7 +151,7 @@ def refresh_data( api_key: str, prompt_registry: PromptRegistry, content_allow_list: ContentAllowList, -): +) -> None: response = fetch_url(f"{base_url}/v1/traceloop/prompts", api_key) prompt_registry.load(response) @@ -159,7 +159,7 @@ def refresh_data( content_allow_list.load(response) -def monitor_exit(exit_event: Event): +def monitor_exit(exit_event: Event) -> None: main_thread = threading.main_thread() main_thread.join() exit_event.set() diff --git a/packages/traceloop-sdk/traceloop/sdk/images/image_uploader.py b/packages/traceloop-sdk/traceloop/sdk/images/image_uploader.py index e9644dcc16..cdd47f194a 100644 --- a/packages/traceloop-sdk/traceloop/sdk/images/image_uploader.py +++ b/packages/traceloop-sdk/traceloop/sdk/images/image_uploader.py @@ -6,22 +6,26 @@ class ImageUploader: - def __init__(self, base_url, api_key): + def __init__(self, base_url: str, api_key: str) -> None: self.base_url = base_url self.api_key = api_key self.logger = logging.getLogger(__name__) - def upload_base64_image(self, trace_id, span_id, image_name, image_file): - asyncio.run(self.aupload_image_file(trace_id, span_id, image_name, image_file)) + def upload_base64_image( + self, trace_id: str, span_id: str, image_name: str, image_file: str + ) -> None: + asyncio.run(self.aupload_base64_image(trace_id, span_id, image_name, image_file)) - async def aupload_base64_image(self, trace_id, span_id, image_name, image_file): + async def aupload_base64_image( + self, trace_id: str, span_id: str, image_name: str, image_file: str + ) -> str: url = self._get_image_url(trace_id, span_id, image_name) await self._async_upload(url, image_file) return url - def _get_image_url(self, trace_id, span_id, image_name): + def _get_image_url(self, trace_id: str, span_id: str, image_name: str) -> str: response = requests.post( f"{self.base_url}/v2/traces/{trace_id}/spans/{span_id}/images", json={ @@ -33,9 +37,9 @@ def _get_image_url(self, trace_id, span_id, image_name): }, ) - return response.json()["url"] + return response.json()["url"] # type: ignore[no-any-return] - async def _async_upload(self, url, base64_image): + async def _async_upload(self, url: str, base64_image: str) -> None: headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", diff --git a/packages/traceloop-sdk/traceloop/sdk/logging/logging.py b/packages/traceloop-sdk/traceloop/sdk/logging/logging.py index 9a6f1dd193..310228a1cc 100644 --- a/packages/traceloop-sdk/traceloop/sdk/logging/logging.py +++ b/packages/traceloop-sdk/traceloop/sdk/logging/logging.py @@ -1,5 +1,5 @@ import logging -from typing import Dict +from typing import Dict, Optional, Any from opentelemetry.exporter.otlp.proto.grpc._log_exporter import ( OTLPLogExporter as GRPCExporter, @@ -15,13 +15,13 @@ class LoggerWrapper(object): - resource_attributes: dict = {} - endpoint: str = None + resource_attributes: Dict[Any, Any] = {} + endpoint: Optional[str] = None headers: Dict[str, str] = {} - __logging_exporter: LogExporter = None - __logging_provider: LoggerProvider = None + __logging_exporter: Optional[LogExporter] = None + __logging_provider: Optional[LoggerProvider] = None - def __new__(cls, exporter: LogExporter = None) -> "LoggerWrapper": + def __new__(cls, exporter: Optional[LogExporter] = None) -> "LoggerWrapper": if not hasattr(cls, "instance"): obj = cls.instance = super(LoggerWrapper, cls).__new__(cls) if not LoggerWrapper.endpoint: @@ -58,7 +58,7 @@ def init_logging_exporter(endpoint: str, headers: Dict[str, str]) -> LogExporter def init_logging_provider( - exporter: LogExporter, resource_attributes: dict = None + exporter: LogExporter, resource_attributes: Optional[Dict[Any, Any]] = None ) -> LoggerProvider: resource = ( Resource.create(resource_attributes) diff --git a/packages/traceloop-sdk/traceloop/sdk/metrics/metrics.py b/packages/traceloop-sdk/traceloop/sdk/metrics/metrics.py index 7617b3bc1f..02d74dda94 100644 --- a/packages/traceloop-sdk/traceloop/sdk/metrics/metrics.py +++ b/packages/traceloop-sdk/traceloop/sdk/metrics/metrics.py @@ -1,5 +1,5 @@ from collections.abc import Sequence -from typing import Dict +from typing import Dict, Optional, Any from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( OTLPMetricExporter as GRPCExporter, @@ -20,14 +20,14 @@ class MetricsWrapper(object): - resource_attributes: dict = {} - endpoint: str = None + resource_attributes: Dict[Any, Any] = {} + endpoint: Optional[str] = None # if it needs headers? headers: Dict[str, str] = {} - __metrics_exporter: MetricExporter = None - __metrics_provider: MeterProvider = None + __metrics_exporter: Optional[MetricExporter] = None + __metrics_provider: Optional[MeterProvider] = None - def __new__(cls, exporter: MetricExporter = None) -> "MetricsWrapper": + def __new__(cls, exporter: Optional[MetricExporter] = None) -> "MetricsWrapper": if not hasattr(cls, "instance"): obj = cls.instance = super(MetricsWrapper, cls).__new__(cls) if not MetricsWrapper.endpoint: @@ -66,7 +66,7 @@ def init_metrics_exporter(endpoint: str, headers: Dict[str, str]) -> MetricExpor def init_metrics_provider( - exporter: MetricExporter, resource_attributes: dict = None + exporter: MetricExporter, resource_attributes: Optional[Dict[Any, Any]] = None ) -> MeterProvider: resource = ( Resource.create(resource_attributes) diff --git a/packages/traceloop-sdk/traceloop/sdk/telemetry.py b/packages/traceloop-sdk/traceloop/sdk/telemetry.py index 82074dbf4a..7a469fbcf4 100644 --- a/packages/traceloop-sdk/traceloop/sdk/telemetry.py +++ b/packages/traceloop-sdk/traceloop/sdk/telemetry.py @@ -3,6 +3,7 @@ from pathlib import Path import logging import sys +from typing import Optional, Any from posthog import Posthog from traceloop.sdk.version import __version__ @@ -13,7 +14,9 @@ class Telemetry: ANON_ID_PATH = str(Path.home() / ".cache" / "traceloop" / "telemetry_anon_id") UNKNOWN_ANON_ID = "UNKNOWN" - _posthog: Posthog = None + _posthog: Optional[Posthog] = None + _telemetry_enabled: bool + _curr_anon_id: Optional[str] def __new__(cls) -> "Telemetry": if not hasattr(cls, "instance"): @@ -65,30 +68,31 @@ def _context(self) -> dict: def capture(self, event: str, event_properties: dict = {}) -> None: try: # don't fail if telemetry fails - if self._telemetry_enabled: + if self._telemetry_enabled and self._posthog is not None: self._posthog.capture( self._anon_id(), event, {**self._context(), **event_properties} ) except Exception: pass - def log_exception(self, exception: Exception): + def log_exception(self, exception: Exception) -> Any: try: # don't fail if telemetry fails - return self._posthog.capture( - self._anon_id(), - "exception", - { - **self._context(), - "exception": str(exception), - }, - ) + if self._posthog is not None: + return self._posthog.capture( + self._anon_id(), + "exception", + { + **self._context(), + "exception": str(exception), + }, + ) except Exception: pass - def feature_enabled(self, key: str): + def feature_enabled(self, key: str) -> bool | None: try: # don't fail if telemetry fails - if self._telemetry_enabled: - return self._posthog.feature_enabled(key, self._anon_id()) + if self._telemetry_enabled and self._posthog is not None: + return self._posthog.feature_enabled(key, self._anon_id()) # type: ignore[no-any-return] except Exception: pass return False From 5ea4dd6a0dc11783b50703e3bb1a4b2a46583e24 Mon Sep 17 00:00:00 2001 From: doronkopit5 Date: Tue, 25 Nov 2025 09:21:20 +0200 Subject: [PATCH 3/3] fixes --- packages/traceloop-sdk/tests/test_user_feedback.py | 12 ++++++------ .../traceloop/sdk/annotation/base_annotation.py | 12 ++++++------ .../traceloop/sdk/annotation/user_feedback.py | 8 ++++---- .../traceloop/sdk/decorators/__init__.py | 4 ++-- .../traceloop-sdk/traceloop/sdk/decorators/base.py | 4 ++-- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/traceloop-sdk/tests/test_user_feedback.py b/packages/traceloop-sdk/tests/test_user_feedback.py index c15bf4b62f..354d576ada 100644 --- a/packages/traceloop-sdk/tests/test_user_feedback.py +++ b/packages/traceloop-sdk/tests/test_user_feedback.py @@ -38,7 +38,7 @@ def test_user_feedback_initialization(mock_http): def test_create_basic_feedback(user_feedback: UserFeedback, mock_http: Mock): """Test creating basic user feedback""" user_feedback.create( - annotation_task="task_123", entity_instance_id="instance_456", tags={"sentiment": "positive"} + annotation_task="task_123", entity_id="instance_456", tags={"sentiment": "positive"} ) mock_http.post.assert_called_once_with( @@ -60,7 +60,7 @@ def test_create_feedback_complex_tags(user_feedback: UserFeedback, mock_http: Mo """Test creating user feedback with complex tags""" tags = {"sentiment": "positive", "relevance": 0.95, "tones": ["happy", "nice"]} - user_feedback.create(annotation_task="task_123", entity_instance_id="instance_456", tags=tags) + user_feedback.create(annotation_task="task_123", entity_id="instance_456", tags=tags) mock_http.post.assert_called_once_with( "annotation-tasks/task_123/annotations", @@ -80,10 +80,10 @@ def test_create_feedback_complex_tags(user_feedback: UserFeedback, mock_http: Mo def test_create_feedback_parameter_validation(user_feedback: UserFeedback): """Test parameter validation for feedback creation""" with pytest.raises(ValueError, match="annotation_task is required"): - user_feedback.create(annotation_task="", entity_instance_id="instance_456", tags={"sentiment": "positive"}) + user_feedback.create(annotation_task="", entity_id="instance_456", tags={"sentiment": "positive"}) - with pytest.raises(ValueError, match="entity_instance_id is required"): - user_feedback.create(annotation_task="task_123", entity_instance_id="", tags={"sentiment": "positive"}) + with pytest.raises(ValueError, match="entity_id is required"): + user_feedback.create(annotation_task="task_123", entity_id="", tags={"sentiment": "positive"}) with pytest.raises(ValueError, match="tags cannot be empty"): - user_feedback.create(annotation_task="task_123", entity_instance_id="instance_456", tags={}) + user_feedback.create(annotation_task="task_123", entity_id="instance_456", tags={}) diff --git a/packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py b/packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py index 44d1cb838d..b9e515ca0c 100644 --- a/packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py +++ b/packages/traceloop-sdk/traceloop/sdk/annotation/base_annotation.py @@ -21,7 +21,7 @@ def __init__(self, http: HTTPClient, app_name: str, flow: str): def create( self, annotation_task: str, - entity_instance_id: str, + entity_id: str, tags: Dict[str, Any], ) -> None: """Create an user feedback annotation for a specific task. @@ -29,7 +29,7 @@ def create( Args: annotation_task (str): The ID/slug of the annotation task to report to. Can be found at app.traceloop.com/annotation_tasks/:annotation_task_id - entity_id (str): The ID of the specific entity instance being annotated, should be reported + entity_id (str): The ID of the specific entity being annotated, should be reported in the association properties tags (Dict[str, Any]): Dictionary containing the tags to be reported. Should match the tags defined in the annotation task @@ -39,7 +39,7 @@ def create( client = Client(api_key="your-key") client.annotation.create( annotation_task="task_123", - entity_id="instance_456", + entity_id="456", tags={ "sentiment": "positive", "relevance": 0.95, @@ -51,15 +51,15 @@ def create( if not annotation_task: raise ValueError("annotation_task is required") - if not entity_instance_id: - raise ValueError("entity_instance_id is required") + if not entity_id: + raise ValueError("entity_id is required") if not tags: raise ValueError("tags cannot be empty") self._http.post( f"annotation-tasks/{annotation_task}/annotations", { - "entity_instance_id": entity_instance_id, + "entity_instance_id": entity_id, "tags": tags, "source": "sdk", "flow": self._flow, diff --git a/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py b/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py index fce3f2790c..8dba6cf07b 100644 --- a/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py +++ b/packages/traceloop-sdk/traceloop/sdk/annotation/user_feedback.py @@ -11,7 +11,7 @@ def __init__(self, http: HTTPClient, app_name: str): def create( self, annotation_task: str, - entity_instance_id: str, + entity_id: str, tags: Dict[str, Any], ) -> None: """Create an annotation for a specific task. @@ -19,7 +19,7 @@ def create( Args: annotation_task (str): The ID/slug of the annotation task to report to. Can be found at app.traceloop.com/annotation_tasks/:annotation_task_id - entity_instance_id (str): The ID of the specific entity instance being annotated, should be reported + entity_id (str): The ID of the specific entity being annotated, should be reported in the association properties tags (Dict[str, Any]): Dictionary containing the tags to be reported. Should match the tags defined in the annotation task @@ -29,7 +29,7 @@ def create( client = Client(api_key="your-key") client.annotation.create( annotation_task="task_123", - entity_instance_id="instance_456", + entity_id="instance_456", tags={ "sentiment": "positive", "relevance": 0.95, @@ -39,4 +39,4 @@ def create( ``` """ - return BaseAnnotation.create(self, annotation_task, entity_instance_id, tags) + return BaseAnnotation.create(self, annotation_task, entity_id, tags) diff --git a/packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py b/packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py index 5af2ffe8e5..0036a99800 100644 --- a/packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py +++ b/packages/traceloop-sdk/traceloop/sdk/decorators/__init__.py @@ -1,4 +1,4 @@ -from typing import Optional, TypeVar, Callable +from typing import Any, Optional, TypeVar, Callable import warnings from opentelemetry.semconv_ai import TraceloopSpanKindValues @@ -8,7 +8,7 @@ entity_method, ) -F = TypeVar("F") +F = TypeVar("F", bound=Callable[..., Any]) def task( diff --git a/packages/traceloop-sdk/traceloop/sdk/decorators/base.py b/packages/traceloop-sdk/traceloop/sdk/decorators/base.py index 2cdcfe633e..e083f03269 100644 --- a/packages/traceloop-sdk/traceloop/sdk/decorators/base.py +++ b/packages/traceloop-sdk/traceloop/sdk/decorators/base.py @@ -2,8 +2,8 @@ from functools import wraps import os from typing import ( - Optional, TypeVar, + Optional, Callable, Any, cast, @@ -27,7 +27,7 @@ from traceloop.sdk.utils import camel_to_snake from traceloop.sdk.utils.json_encoder import JSONEncoder -F = TypeVar("F") +F = TypeVar("F", bound=Callable[..., Any]) def _truncate_json_if_needed(json_str: str) -> str: