Skip to content

Conversation

@nina-kollman
Copy link
Contributor

@nina-kollman nina-kollman commented Aug 11, 2025

  • Add dataset module with Dataset, Column, Row, and Model classes
  • Add datasets module with Datasets management functionality
  • Include comprehensive tests with VCR cassettes
  • Add sample application demonstrating dataset usage

🤖 Generated with Claude Code

  • I have added tests that cover my changes.
  • If adding a new instrumentation or changing an existing one, I've added screenshots from some observability platform showing the change.
  • PR name follows conventional commits format: feat(instrumentation): ... or fix(instrumentation): ....
  • (If applicable) I have updated the documentation accordingly.

Important

Add comprehensive dataset management functionality to the SDK, including creation, retrieval, updating, and deletion of datasets, columns, and rows, with extensive test coverage.

  • New Features
    • Add Dataset, Column, Row, and Model classes in dataset module for dataset management.
    • Add Datasets class in datasets module for managing datasets, including creation from CSV/DataFrame, listing, retrieving, deleting, publishing, and downloading versioned CSVs.
    • Integrate dataset management into Client class in client.py.
  • Tests
    • Add extensive tests for dataset CRUD operations, column and row management, publishing, and version downloads using VCR cassettes.
    • Add test_constants.py for reusable test constants.
  • Misc
    • Update pyproject.toml to include pandas as a dependency for dataset functionality.
    • Add sample application dataset_example.py demonstrating dataset usage.

This description was created by Ellipsis for d852d49. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Full dataset management in the SDK: create from CSV/DataFrame, list, retrieve, delete, publish, download versioned CSVs, manage columns/rows; datasets exposed on the main client; added an end-to-end demo script with AI-generated sample data examples.
  • Tests

    • Large expansion of VCR-backed fixtures and pytest suites covering dataset CRUD, columns, rows, publishing, version downloads, error scenarios, and a new datasets test fixture.
  • Chores

    • Updated .gitignore and test deps (vcrpy bump, added pandas) and packaging extras.

- Add dataset module with Dataset, Column, Row, and Model classes
- Add datasets module with Datasets management functionality
- Include comprehensive tests with VCR cassettes
- Add sample application demonstrating dataset usage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Nina Kollman seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link

coderabbitai bot commented Aug 11, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • packages/traceloop-sdk/poetry.lock is excluded by !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a datasets feature: new dataset domain models and manager (Dataset, Row, Column, Datasets), dataset API Pydantic models, HTTP client methods (GET CSV, PUT, DELETE), Client.datasets wiring, many VCR test cassettes and pytest suites/fixtures, a sample-app demo, pandas test dependency, and a .gitignore entry for .claude.

Changes

Cohort / File(s) Summary
Repo config
**/.gitignore
Adds .claude to ignore and adjusts newline near chroma.sqlite3.
SDK client wiring
packages/traceloop-sdk/traceloop/sdk/client/client.py
Adds public datasets: Datasets attribute and initializes it in Client.init.
HTTP client
packages/traceloop-sdk/traceloop/sdk/client/http.py
GET returns CSV text when Content-Type is text/csv; adds PUT and DELETE methods; expands exception handling and logging.
Dataset package exports
packages/traceloop-sdk/traceloop/sdk/dataset/__init__.py
Re-exports public dataset symbols and defines all.
Dataset models
packages/traceloop-sdk/traceloop/sdk/dataset/model.py
Adds ColumnType enum and numerous Pydantic request/response models for dataset lifecycle.
Domain classes
packages/traceloop-sdk/traceloop/sdk/dataset/base.py, .../dataset.py, .../column.py, .../row.py
Adds BaseDatasetEntity, Dataset, Column, and Row classes with HTTP-backed operations (create/update/delete columns & rows, publish, helpers to materialize from API responses).
Datasets manager
packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py
New Datasets wrapper: list/get/delete datasets; create from CSV/DataFrame (type inference, slugify); get_version_csv; internal _create_dataset and _slugify; optional pandas support.
Tests: fixtures & constants
packages/traceloop-sdk/tests/conftest.py, packages/traceloop-sdk/tests/datasets/test_constants.py
Adds datasets pytest fixture returning a Datasets instance and a centralized TestConstants class; minor formatting edits.
Tests: suites
packages/traceloop-sdk/tests/datasets/*, packages/traceloop-sdk/tests/dataset/*
Adds multiple pytest modules covering create-from-CSV/DataFrame, publish, get-by-slug/version, list, delete, column and row operations, and error-path tests (VCR-marked).
VCR cassettes
packages/traceloop-sdk/tests/**/cassettes/...
Adds many cassette YAML fixtures covering create (CSV/DataFrame), publish, add rows, delete, get/version CSV, duplicate/validation errors, and 404/401 scenarios.
Sample app demo
packages/sample-app/sample_app/dataset_example.py
New standalone demo script showing dataset flows (CSV/DataFrame ingestion, add/update/delete columns/rows, publish/get/delete), including OpenAI-enabled examples.
Packaging / deps
packages/traceloop-sdk/pyproject.toml
Bumps vcrpy test dep, adds pandas to test dependencies, and adds extras datasets = ["pandas"].

Sequence Diagram(s)

sequenceDiagram
  participant App as App / Tests / Sample
  participant Client as Client
  participant Datasets as Datasets
  participant Dataset as Dataset
  participant HTTP as HTTPClient
  participant API as Traceloop API

  App->>Client: init Client(api_key)
  Client->>Datasets: expose datasets wrapper
  App->>Datasets: from_csv()/from_dataframe()
  Datasets->>HTTP: POST /v2/datasets (CreateDatasetRequest)
  HTTP->>API: POST /v2/datasets
  API-->>HTTP: 201 CreateDatasetResponse
  HTTP-->>Datasets: response
  Datasets-->>App: Dataset instance

  App->>Dataset: add_rows(rows)
  Dataset->>HTTP: POST /v2/datasets/{slug}/rows
  API-->>HTTP: 201 CreateRowsResponse
  HTTP-->>Dataset: new rows

  App->>Dataset: add_column(...)
  Dataset->>HTTP: POST /v2/datasets/{slug}/columns
  API-->>HTTP: 201 AddColumnResponse
  HTTP-->>Dataset: column

  App->>Dataset: publish()
  Dataset->>HTTP: POST /v2/datasets/{slug}/publish
  API-->>HTTP: 200 PublishDatasetResponse
  HTTP-->>Dataset: version

  App->>Datasets: get_version_csv(slug,version)
  Datasets->>HTTP: GET /v2/datasets/{slug}/versions/{version}
  API-->>HTTP: 200 text/csv
  HTTP-->>Datasets: CSV string
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • doronkopit5

Poem

A nibble of data, a hop through the fields,
Columns like carrots, rows in tidy yields.
I press "publish" — thump! — a version takes flight,
CSV breezes hum through the night.
With pandas or none, I bound and I play — datasets bloom today. 🐇📊

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nk/datasets-clean-v2

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@nina-kollman nina-kollman marked this pull request as ready for review August 11, 2025 20:17
Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed everything up to 9632f15 in 2 minutes and 8 seconds. Click for details.
  • Reviewed 2923 lines of code in 40 files
  • Skipped 1 files when reviewing.
  • Skipped posting 8 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/sample-app/sample_app/dataset_example.py:14
  • Draft comment:
    Sample usage looks clear, but consider using a logging framework instead of print statements for production-level diagnostics.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is an example/demo script meant to demonstrate functionality in a clear way. Print statements are actually more appropriate here since they make the example more readable and approachable. Using a logging framework would add complexity that distracts from the main purpose. The docstring explicitly states this is an "Example script demonstrating the Traceloop Dataset functionality". The comment has a valid point that logging frameworks are generally better for production code. Maybe some users might copy this code directly into production. This is clearly marked as example code, not production code. Making it more complex would hurt its primary purpose of being a clear demonstration. Users should know to adapt example code for production use. Delete the comment. Print statements are appropriate for an example script where clarity and simplicity are priorities.
2. packages/traceloop-sdk/traceloop/sdk/client/http.py:31
  • Draft comment:
    Error handling uses print with colorama; consider integrating a logging framework to better manage error output.
  • Reason this comment was not posted:
    Comment was on unchanged code.
3. packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py:115
  • Draft comment:
    The _create_rows method conditionally appends rows using an if/else that checks for truthiness of self.rows. Since self.rows is initialized as an empty list, consider always using append for clarity.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% The code's behavior of checking self.rows truthiness actually seems intentional to handle both cases where rows is None or an empty list. The comment assumes self.rows is always a list due to init, but misses that it could be None due to setattr(). The current implementation is more robust by handling both cases. Am I certain that setattr() could actually set rows to None? Maybe there's another reason for this pattern that I'm missing? Yes, looking at line 47, setattr() is called with response.model_dump() which could definitely overwrite rows with None since rows is Optional in the class definition. The comment should be deleted because it incorrectly assumes self.rows is always a list, when in fact the if/else pattern is intentionally handling cases where it could be None.
4. packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py:169
  • Draft comment:
    The _slugify method manually implements slugification. Consider using a dedicated slugify library (e.g., 'python-slugify') to handle edge cases and improve maintainability.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 30% vs. threshold = 50% While using a dedicated library could handle more edge cases, the current implementation is simple, focused, and handles the core cases needed. The code is clear and maintainable. Adding a dependency just for this simple operation may be overkill. The current implementation gives more control over the exact slugification rules needed. The comment has merit - a dedicated library could handle Unicode characters and other edge cases better. The current implementation might break with international characters. The current implementation seems sufficient for the immediate needs, and the code is clear and maintainable. If Unicode support becomes necessary, it can be added later. While the suggestion has merit, the current implementation is adequate and adding a dependency for this simple operation may be unnecessary. The comment should be removed.
5. packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_get_dataset_with_columns.yaml:19
  • Draft comment:
    The JSON string for the column names in the response appears to have inconsistent line breaks. 'New Column 1' and 'New Column 3' are split over two lines (e.g., see line 19-20), while 'New Column 2' is on a single line. Please verify if these line breaks are intentional or if they should be formatted consistently.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is a test fixture file containing recorded API responses. The line breaks in the JSON string are not a functional issue - they're just an artifact of YAML formatting for readability. The actual JSON string will be parsed correctly regardless of these line breaks. This seems like a purely cosmetic issue that doesn't affect functionality. Maybe inconsistent formatting could make the test file harder to read and maintain over time? Maybe there's a standard formatting we should follow? The line breaks are handled automatically by the YAML parser and don't affect the actual JSON content. This is not worth enforcing consistency on as it's just test fixture data. Delete this comment as it's discussing purely cosmetic formatting in a test fixture file that doesn't impact functionality.
6. packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_get_dataset_by_version.yaml:21
  • Draft comment:
    Typographical suggestion: The CSV content contains the term "hallo". If this is unintentional, it might be meant to be "hello".
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is a test fixture file, likely used for recording and replaying API responses. The content "hallo" could be intentional test data. Even if it's not, changing test data spelling is extremely low priority and doesn't affect functionality. This seems like an unnecessary nitpick for test data. Maybe the spelling consistency across test files is important for maintainability? Maybe this is part of a larger test suite where "hello" is the expected value? Without seeing other test files or understanding the full test context, we can't know if this spelling matters. The comment is speculative and not clearly actionable. Delete the comment. Suggesting spelling changes in test fixture data is not clearly useful and could be interfering with intentional test cases.
7. packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_version_csv.yaml:21
  • Draft comment:
    Typo alert: The CSV data includes the value 'hallo'. If this was intended to be 'hello', please update accordingly.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is a test fixture file containing recorded HTTP responses. 'Hallo' could be intentional test data - it's a common German greeting. Even if it's a typo, it's test data that likely doesn't impact functionality. The exact content of test data usually doesn't matter as long as the tests pass. Making purely cosmetic changes to test fixtures isn't valuable. Maybe the spelling is actually important for the test case? Maybe this is supposed to match some expected value? If the spelling was important, the test would already be failing. Since this is a newly added test file that presumably passes, we can assume the spelling is intentional or irrelevant. This comment should be deleted. It's making a cosmetic suggestion about test data without clear evidence that it's actually incorrect or important.
8. packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py:21
  • Draft comment:
    Typographical suggestion: The class docstring on line 21 reads "Dataset class dataset API communication". Consider rephrasing it to "Dataset class for API communication" for better clarity.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50% While the suggestion would improve readability, our rules state not to make purely informative comments or comments about obvious/unimportant things. This is a minor wording improvement that doesn't affect functionality. The current docstring, while not perfect, is still understandable. The docstring is awkwardly phrased and could confuse new developers. Clear documentation is important for maintainability. While documentation clarity is valuable, this is too minor of an issue to warrant a PR comment. The meaning is still clear enough despite the awkward phrasing. Delete this comment as it's too minor and doesn't highlight any significant issues that need addressing.

Workflow ID: wflow_5Q8vr9JlLJFUeLd3

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🔭 Outside diff range comments (1)
packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_version_csv_failure.yaml (1)

1-55: Ensure comprehensive VCR secret redaction

VCR is currently only filtering the authorization header. To prevent other credentials from leaking into cassettes, update your VCR configuration in packages/traceloop-sdk/tests/conftest.py as follows:

• Include all sensitive headers in filter_headers
• Add filter_query_parameters for any secret params
• (Optional) Use a before_record hook to scrub tokens in bodies

Suggested diff:

--- a/packages/traceloop-sdk/tests/conftest.py
+++ b/packages/traceloop-sdk/tests/conftest.py
@@ -44,7 +44,9 @@ def pytest_configure(config):
     VCR.configure(**{
       "cassette_library_dir": "tests/datasets/cassettes",
       "record_mode": os.getenv("VCR_RECORD_MODE", "none"),
-      "filter_headers": ["authorization"],
+      "filter_headers": [
+        "authorization", "x-api-key", "api-key", "bearer", "basic"
+      ],
+      "filter_query_parameters": ["api_key", "token"],
       "ignore_localhost": True,
       "allow_unused_http_interactions": False,
       "debug_logger": False,
🧹 Nitpick comments (32)
.gitignore (3)

181-182: Clarify intent: ignore Claude directory explicitly

If this is the Claude Code workspace directory, prefer a trailing slash to target the directory and avoid matching files named .claude.

-# Claude
-.claude
+# Claude Code workspace
+.claude/

179-179: Also ignore SQLite journal for Chroma DB

Tests/tools can create the SQLite write-ahead/journal file. Add it to avoid accidental commits.

 chroma.sqlite3
+chroma.sqlite3-journal

178-178: Typo: “artifcats” → “artifacts”

Small spelling fix in the section header.

-# Test artifcats
+# Test artifacts
packages/traceloop-sdk/traceloop/sdk/client/http.py (3)

44-49: Generalize CSV detection and add a fallback using Content-Disposition.

Some servers use application/csv or set only Content-Disposition. Make CSV detection broader.

-            content_type = response.headers.get('content-type', '').lower()
-            if 'text/csv' in content_type:
+            content_type = response.headers.get('content-type', '').lower()
+            content_disposition = response.headers.get('content-disposition', '').lower()
+            if 'csv' in content_type or ('.csv' in content_disposition):
                 return response.text
             else:
                 return response.json()

33-33: Use logging instead of print for library code.

Printing to stdout isn’t ideal for SDKs; use the logging module with proper levels.

Outside these ranges:

import logging
logger = logging.getLogger(__name__)

Then replace prints:

-            print(Fore.RED + f"Error making request to {path}: {str(e)}" + Fore.RESET)
+            logger.error("Error making request to %s: %s", path, e)

Also applies to: 51-51, 64-64, 80-80


23-23: Avoid repeating URL construction; centralize _url().

Minor DRY improvement: centralize f"{self.base_url}/v2/{path.lstrip('/')}" in a helper.

Outside these ranges:

def _url(self, path: str) -> str:
    return f"{self.base_url}/v2/{path.lstrip('/')}"

And use:

- response = requests.get(f"{self.base_url}/v2/{path.lstrip('/')}", ...)
+ response = requests.get(self._url(path), ...)

Also applies to: 36-36, 54-54, 67-67

packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_version_csv.yaml (1)

1-61: LGTM: CSV cassette aligns with HTTPClient CSV handling.

Works with the client’s CSV branch. If tests do string comparisons, consider stripping trailing blank lines for stability.

packages/traceloop-sdk/tests/datasets/test_constants.py (1)

6-75: Tighten constants typing and portability (Final/Decimal/Path).

Nice centralization. Optional improvements:

  • Mark constants as Final for clarity.
  • Use Decimal for currency-like values to avoid float surprises in assertions.
  • Use a portable nonexistent path via pathlib.

Example:

from decimal import Decimal
from pathlib import Path
from typing import Final

class TestConstants:
    LAPTOP_PRICE: Final = Decimal("999.99")
    MOUSE_PRICE: Final = Decimal("29.99")
    NON_EXISTENT_FILE_PATH: Final = str(Path.cwd() / "this_file_should_not_exist_12345.csv")

Also double-check usages where “Price” is modeled as string vs number across tests to avoid type mismatch (string "99.99" vs numeric 99.99).

packages/traceloop-sdk/traceloop/sdk/client/client.py (1)

43-44: Initialize Datasets on Client; consider docstring and future extensibility.

  • Please update the Client docstring to mention the new datasets attribute for discoverability.
  • Optional: If you anticipate alternate datasets implementations (e.g., mocking), consider allowing injection via an optional constructor arg or a lazy property later.
packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_dataset_row_operations_api_errors.yaml (1)

1-55: Error-path cassette is sound; consider harmonizing test directory naming.
Interaction is valid and useful. Minor: tests live under both tests/dataset and tests/datasets; consider unifying to reduce confusion.

packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_publish_dataset.yaml (1)

1-63: Great happy-path coverage; add failure-path cassettes for robustness.
Consider adding cassettes/tests for:

  • 409 conflict on duplicate slug.
  • 400 validation errors (e.g., invalid column type or missing required fields).
    This will harden client-side error handling logic.
packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_get_dataset_by_version.yaml (1)

15-16: Be explicit with Accept header for CSV.

Request currently sends Accept: /. Prefer Accept: text/csv for content negotiation clarity and to catch server-side regressions.

Also applies to: 31-32

packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_all_datasets.yaml (1)

23-56: Filter volatile headers and relax matching to reduce flakiness.

Headers like CF-RAY, Date, x-kong-*, cf-cache-status are volatile. Ensure VCR matchers ignore them and they’re filtered from recordings to keep diffs stable.

Add in tests’ VCR setup (e.g., conftest.py):

vcr = VCR(
  match_on=["method", "scheme", "host", "port", "path", "query"],
  filter_headers=["Authorization", "User-Agent", "X-Traceloop-SDK-Version"],
  decode_compressed_response=True,
)
packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_dataset_by_slug_failure.yaml (1)

52-53: Assert domain-specific exception mapping and preserve server message.

For 404, prefer raising a clear NotFound (or SDK-specific) exception carrying {"error":"Dataset not found"} rather than a generic HTTPError. Tests should assert exception type and message.

Happy to add an error-mapping layer in the client and update tests accordingly.

packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_create_dataset_and_add_rows.yaml (1)

3-8: Exercise real types (number/boolean) instead of encoding everything as strings.

Columns age/active are typed as string and values "25"/"true". To validate typing/coercion, use number and boolean columns and native values.

Plan:

  • Update request to use "number"/"boolean" types and unquoted JSON values.
  • Re-record cassette after updating client code and test.

Also applies to: 28-29

packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_delete_by_slug.yaml (2)

1-22: Avoid duplicative coverage unless intentional across modules.

There’s another delete-by-slug cassette under tests/dataset. If both exercise the same client path, consider consolidating to reduce maintenance; if they target different layers, keep both but name them accordingly.


13-16: Consider filtering X-Traceloop-SDK-Version from recordings.

SDK version churn will create noisy diffs. Filter this header in VCR to make recordings resilient to version bumps.

packages/traceloop-sdk/tests/dataset/test_dataset_operations.py (2)

19-19: Move imports to module level.

Import statements should be at the top of the file rather than inside functions for better readability and performance.

+import time
+import tempfile
+import os
+
 import pytest

-@pytest.mark.vcr
-def test_publish_dataset(datasets):
-    try:
-        # Create a test dataset first, then try to publish it
-        import time

24-25: Move imports to module level.

These imports should be moved to the top of the file.

-        import tempfile
-        import os
packages/traceloop-sdk/tests/dataset/test_rows_operations.py (1)

1-5: Consider consolidating imports.

Move the scattered imports from inside functions to the module level for better organization.

Add the missing imports at the top:

 import pytest
+import tempfile
+import os
+import time
-import tempfile
-import os
-import time
packages/traceloop-sdk/tests/dataset/test_columns_operations.py (1)

1-5: Move imports to module level.

Consolidate imports at the top of the file instead of scattering them inside functions.

 import pytest
+import tempfile
+import os
+import time
-import tempfile
-import os
-import time
packages/traceloop-sdk/traceloop/sdk/dataset/column.py (1)

36-37: Unnecessary None check.

The _client is always set in the constructor, making this None check redundant.

-        if self._client is None:
-            raise ValueError("Column must be associated with a dataset to delete")
packages/traceloop-sdk/tests/datasets/test_create_dataset.py (2)

29-31: Move time import to module level.

The time import should be at the top of the file with other imports.

+import time
+
 try:
     import pandas as pd
     PANDAS_AVAILABLE = True
 except ImportError:
     PANDAS_AVAILABLE = False

-        # Use unique slug for testing to avoid conflicts
-        import time
-
         unique_slug = f"test-csv-dataset-{int(time.time())}"

69-71: Move time import to module level.

This time import is duplicated and should be moved to the top of the file.

-        # Use unique slug for testing to avoid conflicts
-        import time
-
         unique_slug = f"test-df-dataset-{int(time.time())}"
packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (1)

8-20: Consider using more specific exception types and pytest.raises

The current pattern of catching broad Exception and checking error messages via string containment is fragile. Consider:

  1. Using specific exception types if the SDK defines them
  2. Using pytest.raises with match parameter for cleaner error assertion

Example refactor for one of the test functions:

-    try:
-        dataset = datasets.get_by_slug("test-qa")
-
-        assert isinstance(dataset, Dataset)
-        # Use flexible assertions that work with recorded data
-        assert dataset.slug == "test-qa"
-        assert hasattr(dataset, "name")
-        assert hasattr(dataset, "description")
-        assert len(dataset.columns) >= 0  # Allow for any number of columns
-        assert len(dataset.rows) >= 0  # Allow for any number of rows
-    except Exception as e:
-        # Allow for expected API errors during recording
-        assert "Failed to get dataset" in str(e) or "404" in str(e) or "401" in str(e)
+    dataset = datasets.get_by_slug("test-qa")
+    
+    assert isinstance(dataset, Dataset)
+    assert dataset.slug == "test-qa"
+    assert hasattr(dataset, "name")
+    assert hasattr(dataset, "description")
+    assert len(dataset.columns) >= 0
+    assert len(dataset.rows) >= 0

For error cases, use pytest.raises:

with pytest.raises(Exception, match=r"(Failed to get dataset|404|401)"):
    datasets.get_by_slug("non-existent")

Also applies to: 24-40, 44-50, 54-62, 89-99

packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py (2)

107-107: Remove unnecessary enumerate

The index from enumerate is not being used.

-    for _, row_obj in enumerate(raw_rows):
+    for row_obj in raw_rows:

115-118: Simplify row list initialization

The conditional check for existing rows is unnecessary when appending.

-        if self.rows:
-            self.rows.append(row)
-        else:
-            self.rows = [row]
+        if self.rows is None:
+            self.rows = []
+        self.rows.append(row)
packages/sample-app/sample_app/dataset_example.py (3)

111-111: Add missing type hint for column parameter

The function accepts a column parameter but it's not type-hinted.

-def update_column_example(dataset: Dataset, column: Column):
+def update_column_example(dataset: Dataset, column: Column) -> None:

129-129: Add missing type hint for column parameter

The function accepts a column parameter but it's not type-hinted.

-def delete_column_example(dataset: Dataset, column: Column):
+def delete_column_example(dataset: Dataset, column: Column) -> None:

163-168: Simplify row existence check

After adding rows, the check for dataset.rows and dataset.rows[0] is redundant. The rows list should exist after add_rows.

-        if dataset.rows and dataset.rows[0]:
-            new_row = dataset.rows[0]
+        if dataset.rows:
+            new_row = dataset.rows[-1]  # Get the last added row
             print(f"Added row with ID: {new_row.id}")
             assert len(dataset.rows) == num_rows + 1
             assert new_row.id in [r.id for r in dataset.rows]
             return new_row
packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py (2)

91-91: Remove unnecessary enumerate

The index from enumerate is not being used.

-            for _, row_data in enumerate(reader):
+            for row_data in reader:

169-171: Move import to module level

Importing re inside the function is inefficient as it happens on every call.

Move the import to the top of the file:

 import csv
+import re
 from typing import List, Optional
 from pathlib import Path

Then remove it from the function:

 def _slugify(self, name: str) -> str:
     """Slugify a name"""
-    import re
     
     slug = name.lower()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3926524 and 9632f15.

📒 Files selected for processing (38)
  • .gitignore (1 hunks)
  • packages/sample-app/sample_app/dataset_example.py (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_create_dataset_with_columns.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_dataset_operations_errors.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_get_dataset_with_columns.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_get_dataset_by_version.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_publish_dataset.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_create_dataset_and_add_rows.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_dataset_deletion.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_dataset_row_operations_api_errors.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/conftest.py (1 hunks)
  • packages/traceloop-sdk/tests/dataset/test_columns_operations.py (1 hunks)
  • packages/traceloop-sdk/tests/dataset/test_dataset_operations.py (1 hunks)
  • packages/traceloop-sdk/tests/dataset/test_rows_operations.py (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_csv.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_dataframe.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_dataframe_with_duplicate_slug.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_with_duplicate_slug.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_delete_by_slug.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_delete_by_slug_failure.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_all_datasets.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_all_datasets_with_invalid_credentials.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_dataset_by_slug.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_dataset_by_slug_failure.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_version_csv.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_version_csv_failure.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/conftest.py (1 hunks)
  • packages/traceloop-sdk/tests/datasets/test_constants.py (1 hunks)
  • packages/traceloop-sdk/tests/datasets/test_create_dataset.py (1 hunks)
  • packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/client/client.py (3 hunks)
  • packages/traceloop-sdk/traceloop/sdk/client/http.py (2 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/__init__.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/column.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/model.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/row.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-04T15:35:30.188Z
Learnt from: nina-kollman
PR: traceloop/openllmetry#3219
File: packages/traceloop-sdk/traceloop/sdk/datasets/dataset.py:357-360
Timestamp: 2025-08-04T15:35:30.188Z
Learning: In the traceloop SDK Dataset class, the HTTP client's put/get/post/delete methods handle exceptions internally, so explicit exception handling in API wrapper methods may not be necessary according to the user's preference.

Applied to files:

  • packages/traceloop-sdk/traceloop/sdk/client/http.py
📚 Learning: 2025-08-04T15:35:30.188Z
Learnt from: nina-kollman
PR: traceloop/openllmetry#3219
File: packages/traceloop-sdk/traceloop/sdk/datasets/dataset.py:357-360
Timestamp: 2025-08-04T15:35:30.188Z
Learning: In the traceloop SDK Dataset class, the established error handling pattern is that HTTP client methods return None on failure (after catching and logging RequestException), and the Dataset API methods check for None return values and raise Exception with descriptive messages. The update_row_api method is inconsistent with this pattern.

Applied to files:

  • packages/traceloop-sdk/traceloop/sdk/dataset/row.py
🪛 Ruff (0.12.2)
packages/traceloop-sdk/tests/datasets/test_datasets_operations.py

92-92: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Lint
  • GitHub Check: Build Packages (3.11)
🔇 Additional comments (24)
packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_csv.yaml (1)

1-63: VCR header filtering is already configured globally

The VCR setup in packages/traceloop-sdk/tests/conftest.py includes a vcr_config fixture that filters the Authorization header:

• packages/traceloop-sdk/tests/conftest.py, vcr_config (line 47):
"filter_headers": ["authorization"],

No further changes are needed here.

packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_with_duplicate_slug.yaml (1)

1-62: Filename intent vs recorded error mismatch.

The file name suggests a duplicate-slug scenario, but the recorded response is about an invalid column slug ("Name"). This can confuse future readers and test intent.

  • Either rename the cassette (and its test) to reflect “invalid column slug” OR re-record the cassette for an actual duplicate-slug path.
  • Verify the associated test asserts the correct error condition.
packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_create_dataset_with_columns.yaml (1)

1-70: LGTM: comprehensive dataset creation cassette.

Structure aligns with Dataset/Column/Row models and column-keyed map in response; good coverage for row_index and timestamps.

packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_all_datasets_with_invalid_credentials.yaml (1)

1-55: Confirm test expectations match client behavior on 401.

HTTPClient.get raises then returns None on 401. Ensure the test asserts None (or appropriate error handling) rather than expecting a value.

packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_get_dataset_with_columns.yaml (1)

1-59: LGTM: dataset retrieval with typed columns and a sample row.

Covers column types (string/number/boolean) and values; good for downstream parsing assertions.

packages/traceloop-sdk/traceloop/sdk/client/client.py (2)

4-4: Import looks correct and aligns with new API surface.
No issues with the import path. Keep it as a single source of truth for datasets functionality.


23-23: No circular-import risk detected
The Datasets class in packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py only imports HTTPClient from traceloop.sdk.client.http, and HTTPClient does not import Client. There is no import of Client back into the datasets module, so no circular dependency exists.

packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_delete_by_slug_failure.yaml (1)

1-57: delete_by_slug already raises on 404 and is covered by tests
The delete_by_slug method in
• packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py (lines 42–46)
returns None on success and raises an Exception(f"Failed to delete dataset {slug}") on any non-2xx response (including 404).
Both
• packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (66–73)
• packages/traceloop-sdk/tests/dataset/test_rows_operations.py (70–75)
catch and assert the exception message for 404 cases. No changes are needed.

packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_dataset_operations_errors.yaml (1)

1-55: Solid negative coverage for columns ops; reuse secret scan.
Cassette looks correct. Ensure your tests assert the precise exception/message for a 404 to keep behavior stable.

You can reuse the secret-scan script from the other cassette comment to ensure no credentials are leaked.

packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_all_datasets.yaml (1)

19-22: Optional description field correctly declared
The DatasetMetadata.description property in packages/traceloop-sdk/traceloop/sdk/dataset/model.py is defined as Optional[str] = None, so items without a description will parse correctly. No changes required.

packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_dataset_by_slug.yaml (1)

19-19: Empty rows are already supported by the Dataset model
Verified that in packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py:

  • rows is declared as Optional[List[Row]] = None
  • the __init__ method sets self.rows = []

No changes are required.

packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_dataframe.yaml (1)

1-66: LGTM! Well-structured test cassette.

The VCR cassette properly records the dataset creation API interaction with appropriate test data and no sensitive information exposure.

packages/traceloop-sdk/traceloop/sdk/dataset/__init__.py (1)

1-6: LGTM! Clean public API structure.

The module exports are well-organized and the __all__ definition properly controls the public API surface.

packages/traceloop-sdk/tests/dataset/test_dataset_operations.py (1)

34-48: LGTM! Proper resource cleanup.

The temporary file handling with try/finally is implemented correctly, ensuring cleanup even on exceptions.

packages/traceloop-sdk/traceloop/sdk/dataset/row.py (1)

38-44: LGTM! Consistent error handling pattern.

The update method follows the established SDK error handling pattern and properly updates local state after successful API calls.

packages/traceloop-sdk/tests/dataset/test_rows_operations.py (2)

24-48: LGTM! Proper test structure and resource cleanup.

The test correctly handles temporary file creation and cleanup, with appropriate error handling for API failures.


70-70: Ignore hardcoded dataset slug in deletion test — it’s stubbed by pytest-vcr

The test_dataset_deletion function is marked with @pytest.mark.vcr and uses a recorded cassette:

  • Cassette path:
    packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_dataset_deletion.yaml
    which contains the DELETE interaction for test-csv-dataset-1754936890.
  • pytest-vcr will replay this recorded response, so the slug doesn’t need to exist in the live API.

You can safely leave the hardcoded slug as is.

Likely an incorrect or invalid review comment.

packages/traceloop-sdk/tests/dataset/test_columns_operations.py (2)

37-41: LGTM! Robust column validation.

The flexible column checking logic properly validates that expected columns exist while being tolerant of different naming conventions.


66-71: Good defensive attribute checking.

The test properly validates that column objects have the expected attributes, using flexible checking for id/slug alternatives.

packages/traceloop-sdk/traceloop/sdk/dataset/column.py (2)

45-49: LGTM! Proper cleanup of column values.

The logic correctly removes the column's values from all existing rows when the column is deleted.


62-72: LGTM! Well-structured conditional updates.

The update method properly builds the payload only with provided values and updates local state after successful API calls.

packages/traceloop-sdk/tests/datasets/test_create_dataset.py (3)

5-13: LGTM! Good conditional import handling.

The pandas availability check is well-implemented, allowing graceful degradation when pandas is not available.


87-95: LGTM! Flexible column validation.

The column checking logic properly validates expected columns while being resilient to naming variations.


163-166: Good error handling for VCR testing.

The exception handling gracefully allows for cases where the duplicate slug test doesn't actually create a duplicate, which is appropriate for VCR testing scenarios.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed 75e5f6b in 1 minute and 13 seconds. Click for details.
  • Reviewed 117 lines of code in 3 files
  • Skipped 1 files when reviewing.
  • Skipped posting 3 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/traceloop-sdk/tests/conftest.py:91
  • Draft comment:
    Refactored formatting in span_postprocess_callback: the attribute redaction (lines 90-94) and on_end reset (lines 106-116) have been reformatted for clarity. No functional changes.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
2. packages/traceloop-sdk/tests/dataset/test_rows_operations.py:69
  • Draft comment:
    Minor formatting and whitespace cleanups in test_rows_operations (e.g. removal of trailing whitespace and adjustment in error condition formatting). No functional changes.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
3. packages/traceloop-sdk/traceloop/sdk/client/client.py:26
  • Draft comment:
    Reformatted the init signature and HTTPClient instantiation for better readability. Functionality remains unchanged.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None

Workflow ID: wflow_VG2VE3d9MnrZgFuK

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
packages/traceloop-sdk/traceloop/sdk/client/client.py (2)

23-23: Document the new public attribute client.datasets.

This expands the public API surface. Please add a short note in the Client docstring (and user docs/README if applicable) showing example usage (e.g., client.datasets.from_csv(...)) and include it in any API reference/CHANGELOG.


26-31: Avoid evaluating sys.argv[0] at function definition time; prefer None defaults.

Default arguments are evaluated once at import time, which can be surprising in tests and embedded contexts. Also, you’re duplicating the default for api_endpoint both here and in the body. Recommend deferring both to initialization logic.

Apply this diff to the signature:

-    def __init__(
-        self,
-        api_key: str,
-        app_name: str = sys.argv[0],
-        api_endpoint: str = "https://api.traceloop.com",
-    ):
+    def __init__(
+        self,
+        api_key: str,
+        app_name: Optional[str] = None,
+        api_endpoint: Optional[str] = None,
+    ):

Additionally, make these changes outside the selected lines:

  • Import Optional from typing at the top of the file.
  • Set the fallback for app_name in the initializer.
from typing import Optional
# Replace the current assignment:
self.app_name = app_name
# With:
self.app_name = app_name or sys.argv[0]
packages/traceloop-sdk/pyproject.toml (1)

74-74: Consider using a more specific pandas version constraint.

While >=1.0.0 allows for maximum flexibility, it may lead to compatibility issues with future major versions of pandas. Consider using a more constrained version range to avoid potential breaking changes.

Apply this diff to use a more conservative version constraint:

-pandas = { version = ">=1.0.0", optional = true }
+pandas = { version = ">=1.0.0,<3.0", optional = true }
packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml (1)

119-119: Add newline at end of file.

YAML files should end with a newline character for POSIX compliance and to avoid potential issues with text processing tools.

Apply this diff to add the missing newline:

-version: 1
+version: 1
+
packages/traceloop-sdk/tests/conftest.py (1)

226-233: Consider adding error handling for missing environment variables.

While the fixture provides sensible defaults, it might be helpful to validate that the HTTP client is properly configured, especially when running tests in different environments.

Consider adding validation to ensure the HTTP client is properly initialized:

 @pytest.fixture
 def datasets():
     """Create a Datasets instance with HTTP client for VCR recording/playback"""
     api_key = os.environ.get("TRACELOOP_API_KEY", "fake-key-for-vcr-playback")
     base_url = os.environ.get("TRACELOOP_BASE_URL", "https://api-staging.traceloop.com")
 
+    if not api_key and os.environ.get("CI"):
+        pytest.skip("TRACELOOP_API_KEY not set in CI environment")
+
     http = HTTPClient(base_url=base_url, api_key=api_key, version="1.0.0")
     return Datasets(http)
packages/traceloop-sdk/tests/dataset/test_rows_operations.py (1)

12-12: Consider extracting timestamp generation to a helper function.

The timestamp generation pattern is repeated. Consider extracting it to reduce duplication.

Add a helper function at the module level:

def _generate_unique_slug(prefix: str) -> str:
    """Generate a unique slug with timestamp suffix."""
    return f"{prefix}-{int(time.time())}"

Then update the tests:

-        unique_slug = f"test-rows-{int(time.time())}"
+        unique_slug = _generate_unique_slug("test-rows")
-        unique_slug = f"test-add-rows-{int(time.time())}"
+        unique_slug = _generate_unique_slug("test-add-rows")

Also applies to: 56-56

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9632f15 and 75e5f6b.

⛔ Files ignored due to path filters (1)
  • packages/traceloop-sdk/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • packages/traceloop-sdk/pyproject.toml (2 hunks)
  • packages/traceloop-sdk/tests/conftest.py (5 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_get_dataset_by_version.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/test_rows_operations.py (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_version_csv.yaml (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/client/client.py (3 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/model.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/row.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_get_dataset_by_version.yaml
  • packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py
  • packages/traceloop-sdk/traceloop/sdk/dataset/row.py
  • packages/traceloop-sdk/traceloop/sdk/dataset/model.py
  • packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_version_csv.yaml
🧰 Additional context used
🪛 YAMLlint (1.37.1)
packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml

[error] 119-119: no new line character at the end of file

(new-line-at-end-of-file)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Lint
🔇 Additional comments (7)
packages/traceloop-sdk/traceloop/sdk/client/client.py (3)

4-4: Importing Datasets into Client is appropriate and clear.

This cleanly surfaces datasets functionality at the client layer.


46-48: Confirmed HTTPClient supports version and propagates it to headers.
The constructor in packages/traceloop-sdk/traceloop/sdk/client/http.py (Line 12) accepts a version: str argument, stores it on self.version, and includes it in the "X-Traceloop-SDK-Version" header (Line 20). No further changes needed.


50-50: Optional pandas import is safely guarded
The datasets.py module wraps the top-level import pandas as pd in a try/except, setting PANDAS_AVAILABLE=False on ImportError, and from_dataframe checks that flag before using pandas. As a result:

  • Users without the pandas extra can still import Client without errors.
  • An explicit guard prevents runtime failures until from_dataframe is called.

If you’re concerned about import-time overhead when pandas is installed, you may choose to move the import pandas (and PANDAS_AVAILABLE logic) inside the from_dataframe method or use a lazy-loaded property. Otherwise, the current pattern fulfills the optional dependency requirement.

packages/traceloop-sdk/pyproject.toml (1)

92-93: LGTM! Clean implementation of optional dependency.

The extras configuration correctly implements pandas as an optional dependency for the datasets feature, following Poetry's standard pattern.

packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml (1)

27-28: Ensure ISO 8601 nanosecond timestamps with “Z” suffix parse correctly

I didn’t find any custom datetime parsing (e.g. datetime.fromisoformat) in the SDK—timestamps for created_at/updated_at flow through Pydantic’s default parser. Note that:

  • Python’s built-in datetime.fromisoformat neither accepts a trailing “Z” nor more than six fractional digits.
  • Pydantic’s parser often handles “Z” and arbitrary-length fractions via its internal dateutil logic, but behavior can vary by version and environment.

Please verify that 2025-08-11T19:00:00.000000000Z is consistently parsed on all CI targets (and local dev machines). If you run into inconsistencies, consider normalizing your cassettes to six-digit microsecond precision (e.g. 2025-08-11T19:00:00.000000Z) to avoid cross-platform parsing issues.

packages/traceloop-sdk/tests/conftest.py (1)

91-94: LGTM! Proper multi-line formatting.

The condition has been properly split across multiple lines for better readability.

packages/traceloop-sdk/tests/dataset/test_rows_operations.py (1)

75-86: No case sensitivity issue detected
Column names are preserved exactly as provided (no .lower() transformations in dataset.py), so using "name" in the test is correct. No changes required.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Changes requested ❌

Reviewed f0cdb70 in 1 minute and 49 seconds. Click for details.
  • Reviewed 12 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_aUOlLHnIrxTScy8p

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed 5c6e54b in 1 minute and 38 seconds. Click for details.
  • Reviewed 220 lines of code in 6 files
  • Skipped 0 files when reviewing.
  • Skipped posting 6 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml:84
  • Draft comment:
    The response JSON now includes a 'total' field. Ensure that the client code/tests are updated to assert and handle this 'total' property.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 20% vs. threshold = 50% This is a test cassette file that records API responses for testing. The comment suggests making test changes, but we don't know if this is already handled in the actual test code. Since this is just a recorded response, the actual test code may already be updated. Additionally, asking to "ensure" something is a form of verification request that we should avoid. The new 'total' field could be an important API change that needs test coverage. Maybe dropping this comment could lead to missing test cases. While test coverage is important, this comment is speculative without seeing the actual test code. It's asking for verification rather than pointing out a specific issue. Delete the comment because it's asking for verification without clear evidence of a problem, and we can't determine from just the cassette file if test updates are actually needed.
2. packages/traceloop-sdk/tests/dataset/test_columns_operations.py:9
  • Draft comment:
    Removed extraneous whitespace for better readability. No functional changes detected.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
3. packages/traceloop-sdk/tests/dataset/test_dataset_operations.py:5
  • Draft comment:
    Minor formatting cleanup by removing extra blank lines. Verify that the file unlink call and logic remain unaffected.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
4. packages/traceloop-sdk/tests/dataset/test_rows_operations.py:50
  • Draft comment:
    An explicit f.flush() was added before using the temporary file, ensuring data is written. This cleanup improves reliability.
  • Reason this comment was not posted:
    Comment did not seem useful. Confidence is useful = 0% <= threshold 50% This comment is purely informative, explaining what was done and why it might be beneficial. It doesn't suggest any changes or ask for confirmation on any specific aspect of the code.
5. packages/traceloop-sdk/tests/datasets/test_create_dataset.py:43
  • Draft comment:
    Extra blank lines and trailing whitespace were removed to improve code clarity. No logic changes observed.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
6. packages/traceloop-sdk/tests/datasets/test_datasets_operations.py:17
  • Draft comment:
    Refactored test_get_all_datasets by removing the try/except block. This forces the test to expect a valid list response. Make sure this aligns with intended API behavior under test conditions.
  • Reason this comment was not posted:
    Comment did not seem useful. Confidence is useful = 0% <= threshold 50% The comment is asking the PR author to ensure that the change aligns with the intended API behavior, which violates the rule against asking for confirmation of intention or ensuring behavior. It does not provide a specific suggestion or point out a specific issue with the code.

Workflow ID: wflow_BREvzBElvKQPjpIg

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (2)

79-79: Replace assert False with raise AssertionError (Ruff B011)

If you keep the try/except structure, at minimum replace assert False to avoid being optimized out under python -O. Prefer the pytest.raises refactor suggested above.

-        assert False, "Expected authentication error"
+        raise AssertionError("Expected authentication error")

76-86: Use pytest.raises instead of try/except + assert False (addresses Ruff B011)

This makes the test intent clear, avoids false positives under python -O, and aligns with pytest best practices.

-    try:
-        invalid_datasets.get_all()
-        # If this doesn't raise an exception, the test setup might be wrong
-        assert False, "Expected authentication error"
-    except Exception as exc_info:
-        # Should get authentication error or a generic failure error when using VCR
-        assert (
-            "401" in str(exc_info)
-            or "authentication" in str(exc_info).lower()
-            or "Failed to get datasets" in str(exc_info)
-        )
+    with pytest.raises(Exception) as exc_info:
+        invalid_datasets.get_all()
+    # Should get authentication error or a generic failure error when using VCR
+    assert (
+        "401" in str(exc_info.value)
+        or "authentication" in str(exc_info.value).lower()
+        or "Failed to get datasets" in str(exc_info.value)
+    )
🧹 Nitpick comments (13)
packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml (3)

119-119: Add trailing newline to satisfy YAML linting.

YAMLlint flagged “no new line character at the end of file.” Add a newline after Line 119 to keep linters/CI green.

Apply this diff:

-version: 1
+version: 1
+

19-22: Filter dynamic/irrelevant headers in VCR cassettes to reduce churn and noise.

Headers like User-Agent, X-Traceloop-SDK-Version, Date, CF-RAY, and x-kong-* are highly volatile and add noise to diffs. Configure VCR to filter/drop them during recording to stabilize cassettes.

Example (in tests conftest or VCR setup):

import vcr

vcr = vcr.VCR(
    record_mode="once",
    filter_headers=[
        ('user-agent', None),
        ('x-traceloop-sdk-version', None),
        # If you ever record auth, make sure to redact it:
        ('authorization', 'DUMMY'),
    ],
    before_record_response=lambda response: (
        response.update({
            'headers': {
                k: v for k, v in response.get('headers', {}).items()
                if k.lower() not in {
                    'date', 'cf-ray', 'server', 'cf-cache-status',
                    'x-kong-proxy-latency', 'x-kong-upstream-latency',
                    'x-kong-request-id', 'via'
                }
            }
        }) or response
    ),
)

Re-record this cassette after applying the config to remove the volatile headers found on Line 19-22 and Line 30-59 (first interaction), and Line 76-79 and Line 86-115 (second interaction).

Also applies to: 30-59, 76-79, 86-115


3-7: Stabilize matching by using a JSON-body matcher instead of raw string body.

The request bodies are JSON strings. If field ordering or serialization changes, cassettes can mismatch. Add a custom vcrpy matcher that compares parsed JSON, ignoring key order.

Example matcher:

# conftest.py
import json
import vcr

def json_body_matcher(r1, r2):
    def parse(body):
        if body is None:
            return None
        if isinstance(body, bytes):
            body = body.decode('utf-8')
        try:
            return json.loads(body)
        except Exception:
            return body
    return parse(r1.body) == parse(r2.body)

my_vcr = vcr.VCR(
    record_mode='once',
    match_on=['method', 'scheme', 'host', 'port', 'path', 'query', 'json-body'],
)

vcr.request.matchers.register('json-body', json_body_matcher)

Then use my_vcr.use_cassette(...) in tests when recording/replaying the interactions involving bodies like in Line 3-7 and Line 64.

Also applies to: 64-64

packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_csv.yaml (3)

20-23: Filter out SDK version and other volatile request headers from VCR matching to reduce brittleness

X-Traceloop-SDK-Version and User-Agent will change as the SDK evolves. If VCR is configured to match headers, future bumps will break cassettes.

Preferred fix: in your tests’ VCR configuration (e.g., conftest.py), filter these headers:

# Example in tests/conftest.py
import vcr

vcr = vcr.VCR(
    filter_headers=[
        ('authorization', 'DUMMY'),
        ('x-traceloop-sdk-version', None),
        ('user-agent', None),
        ('accept-encoding', None),
        ('connection', None),
        ('content-length', None),
    ],
    match_on=['method', 'uri', 'body'],  # avoid matching on headers
)

If you choose to slim the cassette manually, you can remove these request headers from the YAML to make it more stable.


31-60: Strip dynamic response headers from cassette to minimize churn

Headers like CF-RAY, Date, Server, x-kong-*, via are infra-generated and change between recordings. Keeping them inflates diffs and can cause false negatives if your harness ever matches headers.

Recommended: configure VCR to filter response headers globally (preferred), or prune them in the cassette. Example VCR setup:

# Example sanitization hook
def before_record_response(response):
    # Remove volatile headers
    for h in [
        'CF-RAY','Date','Server','via','x-kong-request-id',
        'x-kong-proxy-latency','x-kong-upstream-latency',
        'cf-cache-status','Permissions-Policy','referrer-policy',
        'strict-transport-security',
    ]:
        response['headers'].pop(h, None)
    # Normalize header casing too if needed
    return response

vcr = vcr.VCR(before_record_response=before_record_response)

If you prefer a minimal manual edit for this cassette, you can delete those headers under response.headers.


31-38: Header normalization: unusual 'x-content-type' key

The header appears as x-content-type: nosniff, which is nonstandard compared to the typical X-Content-Type-Options: nosniff. Likely an upstream proxy normalization. Not an issue for VCR replay, but another reason to strip infra headers to avoid confusion.

Consider filtering or normalizing this header via VCR hooks as suggested above.

packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_publish_dataset.yaml (3)

18-21: Avoid recording SDK version in cassettes to reduce churn.

The X-Traceloop-SDK-Version header will change as the SDK evolves, causing unnecessary cassette diffs. Add it to VCR’s filtered headers.

You can filter it in your VCR configuration (e.g., tests/conftest.py):

import vcr

vcr = vcr.VCR(
    filter_headers=[
        ('authorization', '<redacted>'),
        ('X-Traceloop-SDK-Version', '<redacted>'),
    ],
)

26-58: Scrub dynamic IDs/timestamps and infra headers from responses to reduce cassette noise.

Fields like id, created_at, updated_at, and infra headers (CF-RAY, x-kong-*, Date, etc.) are highly volatile. Scrubbing them keeps cassettes stable and reviews cleaner without reducing test value.

Consider adding a before_record_response hook to sanitize responses:

# tests/conftest.py
import json
import vcr

def _scrub_response(response):
    # Drop volatile headers
    for h in [
        'Date', 'CF-RAY', 'cf-cache-status', 'x-kong-request-id',
        'x-kong-proxy-latency', 'x-kong-upstream-latency', 'Server', 'via',
        'Permissions-Policy', 'referrer-policy', 'strict-transport-security',
        'x-content-type',
    ]:
        response.get('headers', {}).pop(h, None)

    # Scrub IDs/timestamps in JSON bodies
    body = response.get('body', {})
    headers = response.get('headers', {})
    ctype = (headers.get('Content-Type') or [''])[0]
    if isinstance(body, dict) and 'string' in body and 'application/json' in ctype:
        try:
            data = json.loads(body['string'])
            def walk(node):
                if isinstance(node, dict):
                    for k, v in list(node.items()):
                        if k in ('id', 'dataset_id', 'row_id'):
                            node[k] = '<redacted-id>'
                        elif k in ('created_at', 'updated_at'):
                            node[k] = '<redacted-timestamp>'
                        else:
                            walk(v)
                elif isinstance(node, list):
                    for item in node:
                        walk(item)
            walk(data)
            body['string'] = json.dumps(data, separators=(',', ':'))
        except Exception:
            pass
    return response

vcr = vcr.VCR(
    before_record_response=_scrub_response,
    # Ensure we only match on stable aspects
    match_on=['method', 'uri', 'body'],
)

85-114: Filter infra-specific response headers.

CF/ Kong headers and related latencies are environment-specific and add noise. Prefer filtering them out via VCR config as suggested above.

packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (4)

7-14: Drop redundant hasattr checks in positive-path test

The hasattr assertions don’t enforce correctness and can never fail for these attributes on the Dataset object. Keep the test focused on concrete values.

 def test_get_dataset_by_slug(datasets):
     dataset = datasets.get_by_slug("test-qa")

     assert isinstance(dataset, Dataset)
     assert dataset.slug == "test-qa"
-    assert hasattr(dataset, "name")
-    assert hasattr(dataset, "description")

17-28: Remove tautological length assertion

len(datasets_list) >= 0 is always true; it adds no value.

 def test_get_all_datasets(datasets):
     datasets_list = datasets.get_all()

     assert isinstance(datasets_list, list)
-    assert len(datasets_list) >= 0

     for dataset in datasets_list:
         assert isinstance(dataset, DatasetMetadata)
         assert hasattr(dataset, "id")
         assert hasattr(dataset, "slug")
         assert hasattr(dataset, "name")

8-8: Replace magic strings with shared test constants

Hard-coded slugs and versions (e.g., "test-qa", "v1", "test-csv-dataset-conflict") can drift from cassette data and other tests. Consider centralizing them in tests/datasets/test_constants.py and importing here for consistency.

Also applies to: 33-33, 44-44


6-6: Consolidate repeated VCR markers at module level

You can avoid repeating @pytest.mark.vcr by setting a module-level marker.

Add near the top of the file:

pytestmark = pytest.mark.vcr

Also applies to: 16-16, 30-30, 40-40, 52-52, 63-63, 89-89, 99-99

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0cdb70 and 5c6e54b.

⛔ Files ignored due to path filters (1)
  • packages/traceloop-sdk/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • packages/traceloop-sdk/pyproject.toml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_create_dataset_with_columns.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_publish_dataset.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_create_dataset_and_add_rows.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_dataset_deletion.yaml (1 hunks)
  • packages/traceloop-sdk/tests/dataset/test_columns_operations.py (1 hunks)
  • packages/traceloop-sdk/tests/dataset/test_dataset_operations.py (1 hunks)
  • packages/traceloop-sdk/tests/dataset/test_rows_operations.py (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_csv.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_dataframe.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_dataframe_with_duplicate_slug.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_all_datasets.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_dataset_by_slug.yaml (1 hunks)
  • packages/traceloop-sdk/tests/datasets/test_create_dataset.py (1 hunks)
  • packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_create_dataset_and_add_rows.yaml
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_dataset_by_slug.yaml
🚧 Files skipped from review as they are similar to previous changes (11)
  • packages/traceloop-sdk/pyproject.toml
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_dataframe_with_duplicate_slug.yaml
  • packages/traceloop-sdk/tests/datasets/test_create_dataset.py
  • packages/traceloop-sdk/tests/dataset/test_dataset_operations.py
  • packages/traceloop-sdk/tests/datasets/cassettes/test_datasets_operations/test_get_all_datasets.yaml
  • packages/traceloop-sdk/tests/dataset/test_rows_operations.py
  • packages/traceloop-sdk/tests/dataset/cassettes/test_columns_operations/test_create_dataset_with_columns.yaml
  • packages/traceloop-sdk/tests/dataset/test_columns_operations.py
  • packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_dataframe.yaml
  • packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py
  • packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_dataset_deletion.yaml
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (5)
packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py (1)
  • Dataset (20-118)
packages/traceloop-sdk/traceloop/sdk/dataset/model.py (1)
  • DatasetMetadata (90-98)
packages/traceloop-sdk/tests/conftest.py (1)
  • datasets (227-233)
packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py (5)
  • get_by_slug (50-58)
  • get_all (35-42)
  • get_version_csv (158-163)
  • delete_by_slug (44-48)
  • Datasets (25-200)
packages/traceloop-sdk/traceloop/sdk/client/http.py (1)
  • HTTPClient (7-81)
🪛 YAMLlint (1.37.1)
packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml

[error] 119-119: no new line character at the end of file

(new-line-at-end-of-file)

🪛 Ruff (0.12.2)
packages/traceloop-sdk/tests/datasets/test_datasets_operations.py

79-79: Do not assert False (python -O removes these calls), raise AssertionError()

Replace assert False

(B011)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Lint
🔇 Additional comments (9)
packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_csv.yaml (3)

62-64: LGTM: Correct status and content type for a create operation

201 Created with application/json is appropriate for dataset creation. This cassette looks valid for replay.


25-25: Tests consistently fix the staging host, so cassettes will match

  • In packages/traceloop-sdk/tests/conftest.py (line 230), the datasets fixture defaults
    base_url = os.environ.get("TRACELOOP_BASE_URL", "https://api-staging.traceloop.com")
    ensuring VCR playback always targets the staging URL unless explicitly overridden.
  • In packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (line 70), HTTPClient is also instantiated with
    base_url="https://api-staging.traceloop.com"
  • All other tests either use that fixture or similarly hard-code the staging endpoint, matching the URIs in the cassettes.

No change required—tests and cassettes are aligned.


3-8: All CSV columns default to STRING and slugify supports hyphens

  • The from_csv and from_dataframe methods always set every column’s type to ColumnType.STRING; there is no type inference today.
  • The _slugify function preserves hyphens (e.g. “in-stock” → “in-stock”), so the cassette’s slug matches production behavior.

If you plan to add optional type inference (number/boolean) or broaden slug‐normalization (spaces, underscores, special chars), you’ll need to update the parsing logic in from_csv/from_dataframe and add targeted tests for those cases.

packages/traceloop-sdk/tests/dataset/cassettes/test_dataset_operations/test_publish_dataset.yaml (4)

3-6: Request payload for dataset creation looks correct.

Slug, name, description, columns, and rows are well-formed and align with the API surface introduced in this PR.


62-84: Two-step publish flow recorded correctly; IDs consistent.

The publish response dataset_id matches the created dataset id and returns version "v1". This accurately exercises the creation + publish path.


118-118: Cassette format version is valid.

version: 1 matches standard vcrpy cassette format expectations. No action needed.


1-117: VCR matching excludes headers by default

The pytest-vcr setup in packages/traceloop-sdk/tests/conftest.py only defines a vcr_config fixture with filter_headers = ["authorization"] and does not override match_on. vcrpy’s default match_on list does not include headers, so dynamic request/response headers (e.g. SDK version, dates) are not part of cassette matching. No changes required.

packages/traceloop-sdk/tests/datasets/test_datasets_operations.py (2)

90-96: LGTM: failure test is precise and uses pytest.raises correctly

This negative test is well-structured and checks for expected failure modes.


100-106: LGTM: negative CSV version test is correct

Clear failure expectation with pytest.raises and error message assertions.

Comment on lines +1 to +118
interactions:
- request:
body: '{"slug": "test-add-rows", "name": "Test Add Rows Dataset", "description":
"Dataset for testing add_rows method", "columns": [{"slug": "name", "name": "Name",
"type": "string"}, {"slug": "age", "name": "Age", "type": "string"}, {"slug":
"active", "name": "Active", "type": "string"}], "rows": [{"name": "John", "age":
"25", "active": "true"}, {"name": "Jane", "age": "30", "active": "false"}]}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '385'
Content-Type:
- application/json
User-Agent:
- python-requests/2.32.3
X-Traceloop-SDK-Version:
- 1.0.0
method: POST
uri: https://api-staging.traceloop.com/v2/datasets
response:
body:
string: '{"id":"cme7h3v4d003g0105grqt6test","slug":"test-add-rows","name":"Test
Add Rows Dataset","description":"Dataset for testing add_rows method","columns":{"active":{"slug":"active","name":"Active","type":"string"},"age":{"slug":"age","name":"Age","type":"string"},"name":{"slug":"name","name":"Name","type":"string"}},"created_at":"2025-08-11T19:00:00.000000000Z","updated_at":"2025-08-11T19:00:00.000000000Z","rows":[{"id":"cme7h3v4j003h0105qpcmtest1","row_index":1,"values":{"active":"true","age":"25","name":"John"},"created_at":"2025-08-11T19:00:00.000000000Z","updated_at":"2025-08-11T19:00:00.000000000Z"},{"id":"cme7h3v4j003i0105yuu8test2","row_index":2,"values":{"active":"false","age":"30","name":"Jane"},"created_at":"2025-08-11T19:00:00.000000000Z","updated_at":"2025-08-11T19:00:00.000000000Z"}]}'
headers:
CF-RAY:
- 96d9f229ce2d68a5-FRA
Connection:
- keep-alive
Content-Length:
- '822'
Content-Type:
- application/json; charset=utf-8
Date:
- Mon, 11 Aug 2025 19:00:00 GMT
Permissions-Policy:
- geolocation=(self), microphone=()
Server:
- cloudflare
cf-cache-status:
- DYNAMIC
referrer-policy:
- strict-origin-when-cross-origin
strict-transport-security:
- max-age=7776000; includeSubDomains
via:
- kong/3.7.1
x-content-type:
- nosniff
x-kong-proxy-latency:
- '0'
x-kong-request-id:
- 700c8629469ac535bc8f3a5109b92035
x-kong-upstream-latency:
- '15'
status:
code: 201
message: Created
- request:
body: '{"rows": [{"name": "Alice", "age": "28", "active": "true"}]}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '57'
Content-Type:
- application/json
User-Agent:
- python-requests/2.32.3
X-Traceloop-SDK-Version:
- 1.0.0
method: POST
uri: https://api-staging.traceloop.com/v2/datasets/test-add-rows/rows
response:
body:
string: '{"rows":[{"id":"cme7h3v4j003j0105cfh7test3","row_index":3,"values":{"active":"true","age":"28","name":"Alice"},"created_at":"2025-08-11T19:00:05.000000000Z","updated_at":"2025-08-11T19:00:05.000000000Z"}], "total": 1}'
headers:
CF-RAY:
- 96d9f229ce2d68a6-FRA
Connection:
- keep-alive
Content-Length:
- '174'
Content-Type:
- application/json; charset=utf-8
Date:
- Mon, 11 Aug 2025 19:00:05 GMT
Permissions-Policy:
- geolocation=(self), microphone=()
Server:
- cloudflare
cf-cache-status:
- DYNAMIC
referrer-policy:
- strict-origin-when-cross-origin
strict-transport-security:
- max-age=7776000; includeSubDomains
via:
- kong/3.7.1
x-content-type:
- nosniff
x-kong-proxy-latency:
- '0'
x-kong-request-id:
- 700c8629469ac535bc8f3a5109b92036
x-kong-upstream-latency:
- '8'
status:
code: 201
message: Created
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

No secrets detected; cassette content looks safe.

No Authorization or other sensitive headers/values are present; dataset values are dummy. Good to commit as-is after lint and header filtering improvements.

Optionally verify across the repo that no cassettes contain auth headers:


🏁 Script executed:

#!/bin/bash
# Scan repository for potential auth leakage in YAML cassettes or fixtures
rg -n -S --glob '*.yml' --glob '*.yaml' -e 'Authorization:|authorization:|Bearer\s+[A-Za-z0-9\-._~+/]+=*' -e 'api_key|x-api-key|X-API-Key' || true

Length of output: 8347


Redact dummy API credentials in YAML cassettes

Our scan revealed several recorded test cassettes containing dummy API keys, secret access keys, access key IDs, and OAuth apikey grant strings. Even though these are non-production values, we should scrub them before committing:

• packages/opentelemetry-instrumentation-watsonx/tests/metrics/cassettes/test_watsonx_metrics/test_generate_stream_metrics.yaml
• packages/opentelemetry-instrumentation-watsonx/tests/metrics/cassettes/test_watsonx_metrics/test_generate_metrics.yaml
• packages/opentelemetry-instrumentation-watsonx/tests/traces/cassettes/test_generate/test_generate_text_stream.yaml
• packages/opentelemetry-instrumentation-watsonx/tests/traces/cassettes/test_generate/test_generate.yaml
• packages/opentelemetry-instrumentation-langchain/tests/cassettes/test_agents/*.yaml
• packages/opentelemetry-instrumentation-haystack/tests/cassettes/test_simple_pipeline/test_haystack.yaml

Action items:

  1. Extend our cassette-sanitization to redact body fields and headers for api_key, secret_access_key, access_key_id, and OAuth grant strings.
  2. Re-record or post-process these fixtures to mask those values.
  3. Re-run linting and commit the scrubbed cassettes.
🤖 Prompt for AI Agents
In
packages/traceloop-sdk/tests/dataset/cassettes/test_rows_operations/test_add_rows.yaml
around lines 1 to 118, the cassette files include recorded credentials that must
be redacted; update the cassette-sanitization logic (or VCR config/script) to
strip/mask body fields and headers named api_key, secret_access_key,
access_key_id and any OAuth grant strings (match common grant parameter names
and token-like patterns), then re-record or post-process the listed fixtures to
replace those values with safe placeholders, run the test suite and linters to
ensure no secrets remain, and commit the scrubbed cassettes.

Comment on lines +28 to +29
string: '{"id":"cme7h01j3003a0105krd8c5de","slug":"test-csv-dataset","name":"Test
CSV Dataset","description":"Dataset created from CSV for testing","columns":{"name":{"slug":"name","name":"Name","type":"string"},"price":{"slug":"price","name":"Price","type":"string"},"in-stock":{"slug":"in-stock","name":"In Stock","type":"string"}},"created_at":"2025-08-11T18:53:01.50360709Z","updated_at":"2025-08-11T18:53:01.503607146Z","rows":[{"id":"cme7h01jt003b0105w9q0str5","row_index":1,"values":{"name":"Laptop","price":"999.99","in-stock":"true"},"created_at":"2025-08-11T18:53:01.542730767Z","updated_at":"2025-08-11T18:53:01.542730767Z"},{"id":"cme7h01jt003c0105mvd0d8fx","row_index":2,"values":{"name":"Mouse","price":"29.99","in-stock":"false"},"created_at":"2025-08-11T18:53:01.542730767Z","updated_at":"2025-08-11T18:53:01.542730767Z"}]}'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Avoid asserting on server-generated IDs or timestamps in tests

The cassette includes IDs and created_at/updated_at fields. Make sure tests only assert stable fields (slug, name, description, column names/types, and row values), not IDs or timestamps which will differ across recordings.

You can quickly check assertions:


🏁 Script executed:

#!/bin/bash
rg -n -A 2 -B 2 $'assert.*id|created_at|updated_at' packages/traceloop-sdk/tests/datasets

Length of output: 5668


🏁 Script executed:

#!/bin/bash
# Search for any assertions comparing id or timestamps to literal values in dataset tests
rg -n 'assert .*\.id\s*==|assert .*created_at|assert .*updated_at' packages/traceloop-sdk/tests/datasets

Length of output: 106


Scrub dynamic fields from HTTP fixtures to avoid brittle tests
The recorded cassettes embed server-generated id, created_at, and updated_at values, which will change whenever you re-record. To prevent test breakage:

• Update your VCR/fixture configuration to filter out or sanitize these fields (e.g., via filter_json or a before_record_response hook).
• After adding the filters, re-generate the cassettes under packages/traceloop-sdk/tests/datasets/cassettes/… so they no longer contain literal IDs or timestamps.
• Ensure your tests only assert against stable properties (slug, name, description, column definitions, and row values).

This will make test recordings durable across runs.

🤖 Prompt for AI Agents
In
packages/traceloop-sdk/tests/datasets/cassettes/test_create_dataset/test_create_dataset_from_csv.yaml
around lines 28-29, the cassette contains server-generated ids and timestamps
(id, created_at, updated_at) which make tests brittle; update the VCR/fixture
configuration to scrub these dynamic fields (e.g., add filter_json entries or a
before_record_response hook to replace id/created_at/updated_at values with
stable placeholders), re-record the cassette so it contains the sanitized
placeholders, and adjust tests to only assert on stable properties (slug, name,
description, columns, and row values) rather than the scrubbed fields.

Comment on lines +31 to +38
def test_get_version_csv(datasets):
try:
csv_data = datasets.get_version_csv(slug="test-qa", version="v1")
assert isinstance(csv_data, str)
except Exception as e:
# Allow for expected API errors during recording (dataset might not exist)
assert "Failed to get dataset" in str(e) or "404" in str(e) or "401" in str(e)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Make get_version_csv test strict; don’t pass when the call raises

The current try/except allows the test to pass even if get_version_csv raises. Given you already have a dedicated failure test (test_get_version_csv_failure), this should assert success and fail on exceptions.

 @pytest.mark.vcr
 def test_get_version_csv(datasets):
-    try:
-        csv_data = datasets.get_version_csv(slug="test-qa", version="v1")
-        assert isinstance(csv_data, str)
-    except Exception as e:
-        # Allow for expected API errors during recording (dataset might not exist)
-        assert "Failed to get dataset" in str(e) or "404" in str(e) or "401" in str(e)
+    csv_data = datasets.get_version_csv(slug="test-qa", version="v1")
+    assert isinstance(csv_data, str)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_get_version_csv(datasets):
try:
csv_data = datasets.get_version_csv(slug="test-qa", version="v1")
assert isinstance(csv_data, str)
except Exception as e:
# Allow for expected API errors during recording (dataset might not exist)
assert "Failed to get dataset" in str(e) or "404" in str(e) or "401" in str(e)
@pytest.mark.vcr
def test_get_version_csv(datasets):
csv_data = datasets.get_version_csv(slug="test-qa", version="v1")
assert isinstance(csv_data, str)
🤖 Prompt for AI Agents
In packages/traceloop-sdk/tests/datasets/test_datasets_operations.py around
lines 31 to 38, the test wraps get_version_csv in a try/except which masks
failures and lets the test pass on exceptions; remove the try/except and assert
strictly that datasets.get_version_csv returns a string (i.e., call csv_data =
datasets.get_version_csv(slug="test-qa", version="v1") and then assert
isinstance(csv_data, str)) so any exception will fail the test (the existing
failure test covers expected error cases).

Comment on lines +41 to +49
def test_delete_by_slug(datasets):
try:
# Use a test dataset that's safe to delete
datasets.delete_by_slug("test-csv-dataset-conflict")
except Exception as e:
# Allow for expected API errors (dataset might not exist)
assert (
"Failed to delete dataset" in str(e) or "404" in str(e) or "401" in str(e)
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t swallow exceptions in positive-path delete test

This test should assert successful deletion. Let it fail if the API returns an error; your dedicated failure test already covers the negative case.

 @pytest.mark.vcr
 def test_delete_by_slug(datasets):
-    try:
-        # Use a test dataset that's safe to delete
-        datasets.delete_by_slug("test-csv-dataset-conflict")
-    except Exception as e:
-        # Allow for expected API errors (dataset might not exist)
-        assert (
-            "Failed to delete dataset" in str(e) or "404" in str(e) or "401" in str(e)
-        )
+    # Use a test dataset that's safe to delete (success cassette recorded)
+    datasets.delete_by_slug("test-csv-dataset-conflict")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_delete_by_slug(datasets):
try:
# Use a test dataset that's safe to delete
datasets.delete_by_slug("test-csv-dataset-conflict")
except Exception as e:
# Allow for expected API errors (dataset might not exist)
assert (
"Failed to delete dataset" in str(e) or "404" in str(e) or "401" in str(e)
)
@pytest.mark.vcr
def test_delete_by_slug(datasets):
# Use a test dataset that's safe to delete (success cassette recorded)
datasets.delete_by_slug("test-csv-dataset-conflict")
🤖 Prompt for AI Agents
In packages/traceloop-sdk/tests/datasets/test_datasets_operations.py around
lines 41 to 49, the test swallows exceptions for the positive-path delete by
wrapping datasets.delete_by_slug("test-csv-dataset-conflict") in a try/except
and asserting generic error text; remove the try/except so the test fails on
unexpected errors, call datasets.delete_by_slug(...) directly and then add a
clear assertion that deletion succeeded (e.g., check the method returned a
success value or that a subsequent fetch by slug raises a not-found error) so
this test only asserts the positive deletion path.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Changes requested ❌

Reviewed 28c69ee in 2 minutes and 5 seconds. Click for details.
  • Reviewed 259 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 4 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/sample-app/sample_app/dataset_example.py:119
  • Draft comment:
    Avoid using 'type' as a parameter name in column.update; consider using a consistent name like 'col_type' to match add_column_example and to avoid shadowing the built-in 'type'.
  • Reason this comment was not posted:
    Confidence changes required: 50% <= threshold 50% None
2. packages/sample-app/sample_app/dataset_example.py:105
  • Draft comment:
    Using 'assert' for runtime validations can be unreliable in production (assertions may be disabled). Consider explicit error handling for critical checks.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None
3. packages/sample-app/sample_app/dataset_example.py:320
  • Draft comment:
    Ensure that 'response.choices' is not empty before accessing its first element to avoid potential index errors.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None
4. packages/sample-app/sample_app/dataset_example.py:479
  • Draft comment:
    For production code, consider using a logging framework instead of print statements for better log management and configurability.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None

Workflow ID: wflow_urcrQTU4h4FLceZT

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

]

data = []
for query in queries:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider instantiating the OpenAI client once outside the query loop in create_customer_support_dataset to avoid repeated initialization overhead.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Changes requested ❌

Reviewed 47a9ccf in 1 minute and 28 seconds. Click for details.
  • Reviewed 25 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/sample-app/sample_app/dataset_example.py:389
  • Draft comment:
    The triple-quoted system message may capture extra spaces from indentation. Consider using textwrap.dedent or aligning the string properly.
  • Reason this comment was not posted:
    Marked as duplicate.

Workflow ID: wflow_TQloMHM9bFU2TycA

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
packages/sample-app/sample_app/dataset_example.py (3)

306-318: Move OpenAI client instantiation outside the loop to improve performance

The OpenAI client is being recreated for every query iteration, which is inefficient and can lead to unnecessary overhead.

Move the client instantiation outside the loop:

 def create_customer_support_dataset(slug: str) -> Optional[Dataset]:
     """Create a dataset with real OpenAI customer support interactions"""
     print("\n=== Customer Support Dataset with OpenAI ===")
 
     try:
+        # Initialize OpenAI client once
+        client_openai = openai.OpenAI()
+        
         # Sample customer queries
         queries = [
             "How do I reset my password?",
             "My order hasn't arrived yet, what should I do?",
             "Can I return this item after 30 days?",
             "Do you offer international shipping?",
             "What's your refund policy?",
         ]
 
         data = []
         for query in queries:
             try:
                 # Make OpenAI call for customer support response
-                client_openai = openai.OpenAI()
                 response = client_openai.chat.completions.create(

386-400: Move OpenAI client instantiation outside the nested loops to improve efficiency

The OpenAI client is being recreated for each phrase-language combination, causing unnecessary overhead.

Move the client instantiation outside both loops:

 def create_translation_dataset(slug: str) -> Optional[Dataset]:
     """Create a dataset with OpenAI translation examples"""
     print("\n=== Translation Dataset with OpenAI ===")
 
     try:
+        # Initialize OpenAI client once
+        client_openai = openai.OpenAI()
+        
         # Sample phrases to translate
         phrases = [
             "Hello, how are you today?",
             "Thank you for your help.",
             "Where is the nearest restaurant?",
             "I would like to book a room.",
             "What time does the store close?",
         ]
 
         languages = ["Spanish", "French", "German", "Italian"]
 
         data = []
         for phrase in phrases:
             for lang in languages:
                 try:
                     # Use OpenAI for translation
-                    client_openai = openai.OpenAI()
                     response = client_openai.chat.completions.create(

445-508: Add error handling for main orchestration

The main function lacks error handling, which could cause cascading failures if any operation fails. Individual dataset flows should be isolated from each other.

Wrap each dataset flow in try-except blocks to prevent cascading failures:

 def main():
     print("Traceloop Dataset Examples")
     print("=" * 50)
 
-    ds1 = dataset_from_csv_example("sdk-example-1")
-    if ds1:
-        column = add_column_example(ds1)
-        if column:
-            update_column_example(ds1, column)
-        published_version = publish_dataset_example(ds1)
-        delete_row_example(ds1)
-        if column:
-            delete_column_example(ds1, column)
-        get_dataset_by_slug_example(slug="sdk-example-1")
-        if published_version:
-            get_dataset_by_version_example(
-                slug="sdk-example-1", version=published_version
-            )
-        delete_dataset_example(ds1.slug)
+    try:
+        ds1 = dataset_from_csv_example("sdk-example-1")
+        if ds1:
+            column = add_column_example(ds1)
+            if column:
+                update_column_example(ds1, column)
+            published_version = publish_dataset_example(ds1)
+            delete_row_example(ds1)
+            if column:
+                delete_column_example(ds1, column)
+            get_dataset_by_slug_example(slug="sdk-example-1")
+            if published_version:
+                get_dataset_by_version_example(
+                    slug="sdk-example-1", version=published_version
+                )
+            delete_dataset_example(ds1.slug)
+    except Exception as e:
+        print(f"Error in CSV dataset example: {e}")
 
-    ds2 = dataset_from_dataframe_example("sdk-example-2")
-    if ds2:
-        column = add_column_example(ds2)
-        if column:
-            update_column_example(ds2, column)
-        add_row_example(ds2)
-        update_row_example(ds2)
-        if column:
-            delete_column_example(ds2, column)
-        published_version = publish_dataset_example(ds2)
-        if published_version:
-            get_dataset_by_version_example(
-                slug="sdk-example-2", version=published_version
-            )
-        delete_dataset_example(ds2.slug)
+    try:
+        ds2 = dataset_from_dataframe_example("sdk-example-2")
+        if ds2:
+            column = add_column_example(ds2)
+            if column:
+                update_column_example(ds2, column)
+            add_row_example(ds2)
+            update_row_example(ds2)
+            if column:
+                delete_column_example(ds2, column)
+            published_version = publish_dataset_example(ds2)
+            if published_version:
+                get_dataset_by_version_example(
+                    slug="sdk-example-2", version=published_version
+                )
+            delete_dataset_example(ds2.slug)
+    except Exception as e:
+        print(f"Error in DataFrame dataset example: {e}")
 
     # OpenAI examples
     print("\n" + "=" * 50)
     print("OpenAI Integration Examples")
     print("=" * 50)
 
-    # Customer Support Dataset
-    support_ds = create_customer_support_dataset("openai-support-example")
-    if support_ds:
-        published_version = publish_dataset_example(support_ds)
-        if published_version:
-            get_dataset_by_version_example(
-                slug="openai-support-example", version=published_version
-            )
-        delete_dataset_example(support_ds.slug)
+    try:
+        # Customer Support Dataset
+        support_ds = create_customer_support_dataset("openai-support-example")
+        if support_ds:
+            published_version = publish_dataset_example(support_ds)
+            if published_version:
+                get_dataset_by_version_example(
+                    slug="openai-support-example", version=published_version
+                )
+            delete_dataset_example(support_ds.slug)
+    except Exception as e:
+        print(f"Error in OpenAI customer support example: {e}")
 
-    # Translation Dataset
-    translation_ds = create_translation_dataset("openai-translation-example")
-    if translation_ds:
-        published_version = publish_dataset_example(translation_ds)
-        if published_version:
-            get_dataset_by_version_example(
-                slug="openai-translation-example", version=published_version
-            )
-        delete_dataset_example(translation_ds.slug)
+    try:
+        # Translation Dataset
+        translation_ds = create_translation_dataset("openai-translation-example")
+        if translation_ds:
+            published_version = publish_dataset_example(translation_ds)
+            if published_version:
+                get_dataset_by_version_example(
+                    slug="openai-translation-example", version=published_version
+                )
+            delete_dataset_example(translation_ds.slug)
+    except Exception as e:
+        print(f"Error in OpenAI translation example: {e}")
 
     print("\n" + "=" * 50)
     print("All examples completed!")
🧹 Nitpick comments (2)
packages/sample-app/sample_app/dataset_example.py (2)

339-345: Fix multiline string indentation in fallback response

The multiline string has incorrect indentation that will be preserved in the output, making the response look unprofessional.

Fix the indentation of the multiline string:

                 data.append(
                     {
                         "customer_query": query,
-                        "ai_response": """I apologize, but I'm unable to process your request at the moment.
-                            Please contact our support team directly.""",
+                        "ai_response": "I apologize, but I'm unable to process your request at the moment. Please contact our support team directly.",
                         "timestamp": datetime.now().isoformat(),
                         "query_category": "general_support",
                         "resolved": False,
                     }
                 )

392-395: Fix multiline string formatting in system prompt

The multiline string in the system content has inconsistent formatting that may affect the AI's understanding of the prompt.

Clean up the system prompt formatting:

                         messages=[
                             {
                                 "role": "system",
-                                "content": f"""You are a professional translator.
-                                Translate the following English text to {lang}.
-                                Provide only the translation.""",
+                                "content": f"You are a professional translator. Translate the following English text to {lang}. Provide only the translation.",
                             },
                             {"role": "user", "content": phrase},
                         ],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28c69ee and 47a9ccf.

📒 Files selected for processing (1)
  • packages/sample-app/sample_app/dataset_example.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/sample-app/sample_app/dataset_example.py (7)
packages/traceloop-sdk/traceloop/sdk/__init__.py (2)
  • Traceloop (37-270)
  • init (49-202)
packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py (4)
  • Dataset (20-118)
  • add_column (73-91)
  • add_rows (64-71)
  • publish (57-62)
packages/traceloop-sdk/traceloop/sdk/dataset/model.py (1)
  • ColumnType (7-11)
packages/traceloop-sdk/traceloop/sdk/dataset/column.py (3)
  • Column (10-72)
  • delete (34-49)
  • update (51-72)
packages/traceloop-sdk/traceloop/sdk/dataset/row.py (3)
  • Row (9-44)
  • delete (30-36)
  • update (38-44)
packages/traceloop-sdk/tests/conftest.py (1)
  • datasets (227-233)
packages/traceloop-sdk/traceloop/sdk/datasets/datasets.py (5)
  • from_csv (60-109)
  • from_dataframe (111-156)
  • get_version_csv (158-163)
  • get_by_slug (50-58)
  • delete_by_slug (44-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Lint
🔇 Additional comments (1)
packages/sample-app/sample_app/dataset_example.py (1)

1-512: Overall implementation quality is excellent

The sample application provides comprehensive coverage of the Dataset API functionality with well-structured examples, proper error handling in individual functions, realistic use cases with OpenAI integration, and clean separation of concerns. The code demonstrates best practices for API usage and serves as an excellent reference for users.

Comment on lines +165 to +173
if dataset.rows and dataset.rows[0]:
new_row = dataset.rows[0]
print(f"Added row with ID: {new_row.id}")
assert len(dataset.rows) == num_rows + 1
assert new_row.id in [r.id for r in dataset.rows]
return new_row
else:
print("No row was added")
return None
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect assumption about row ordering after addition

The code assumes that dataset.rows[0] contains the newly added row, but this assumption may not hold true as row ordering is not guaranteed after addition.

The logic incorrectly assumes the first row is the newly added one. Fix this by tracking the row properly:

-        if dataset.rows and dataset.rows[0]:
-            new_row = dataset.rows[0]
+        # Find the newly added row (should be the last one added)
+        if len(dataset.rows) >= num_rows + 1:
+            new_row = dataset.rows[-1]  # Get the last row assuming it's the newest
             print(f"Added row with ID: {new_row.id}")
             assert len(dataset.rows) == num_rows + 1
             assert new_row.id in [r.id for r in dataset.rows]
             return new_row
         else:
             print("No row was added")
             return None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if dataset.rows and dataset.rows[0]:
new_row = dataset.rows[0]
print(f"Added row with ID: {new_row.id}")
assert len(dataset.rows) == num_rows + 1
assert new_row.id in [r.id for r in dataset.rows]
return new_row
else:
print("No row was added")
return None
# Find the newly added row (should be the last one added)
if len(dataset.rows) >= num_rows + 1:
new_row = dataset.rows[-1] # Get the last row assuming it's the newest
print(f"Added row with ID: {new_row.id}")
assert len(dataset.rows) == num_rows + 1
assert new_row.id in [r.id for r in dataset.rows]
return new_row
else:
print("No row was added")
return None
🤖 Prompt for AI Agents
In packages/sample-app/sample_app/dataset_example.py around lines 165 to 173,
the code wrongly assumes the newly added row is dataset.rows[0]; instead,
capture the set of existing row IDs before adding, add the row, assert
len(dataset.rows) == num_rows + 1, then find the new row by identifying the ID
that wasn't present earlier (or by matching the returned row's id if the add
call returns it) and use that row for prints/assertions; avoid indexing
dataset.rows[0] and replace those checks with membership/assertions based on IDs
to reliably identify the added row.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Changes requested ❌

Reviewed 2b9c69a in 1 minute and 58 seconds. Click for details.
  • Reviewed 172 lines of code in 4 files
  • Skipped 0 files when reviewing.
  • Skipped posting 3 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/traceloop-sdk/traceloop/sdk/dataset/base.py:15
  • Draft comment:
    Using datetime.now() as a default parameter can lead to all instances sharing the same timestamp. Consider using None as the default and assigning datetime.now() inside init.
  • Reason this comment was not posted:
    Comment was on unchanged code.
2. packages/traceloop-sdk/traceloop/sdk/dataset/column.py:40
  • Draft comment:
    HTTPClient.delete returns a boolean (False on failure), not None. Use a falsy check (e.g. 'if not result:') to catch failures.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
3. packages/traceloop-sdk/traceloop/sdk/dataset/row.py:33
  • Draft comment:
    HTTPClient.delete returns False on error, so checking 'if result is None:' might miss failures. Consider using 'if not result:' instead.
  • Reason this comment was not posted:
    Comment was on unchanged code.

Workflow ID: wflow_nyLbFq1Ro6zwpErb

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/traceloop-sdk/traceloop/sdk/dataset/column.py (1)

40-46: Deletion failure check is incorrect; also make removal safe

HTTPClient.delete returns a boolean (True on 200/204, False otherwise), never None. The current None check will miss failures and proceed to mutate local state. Also, .remove(self) can raise ValueError if the column isn’t present.

Apply this diff:

-        result = self._http.delete(f"datasets/{self._dataset.slug}/columns/{self.slug}")
-        if result is None:
-            raise Exception(f"Failed to delete column {self.slug}")
+        result = self._http.delete(f"datasets/{self._dataset.slug}/columns/{self.slug}")
+        if not result:
+            raise Exception(f"Failed to delete column {self.slug}")
 
-        self._dataset.columns.remove(self)
+        if self in self._dataset.columns:
+            self._dataset.columns.remove(self)
         self.deleted = True
🧹 Nitpick comments (1)
packages/traceloop-sdk/traceloop/sdk/dataset/column.py (1)

54-66: Minor: avoid shadowing built-in name “type”

Using the name type for parameters can shadow the built-in and reduce readability. Consider renaming to column_type in the future (keeping the JSON field as "type" on the wire).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47a9ccf and 2b9c69a.

📒 Files selected for processing (6)
  • packages/traceloop-sdk/traceloop/sdk/client/http.py (2 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/__init__.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/base.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/column.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/row.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py
  • packages/traceloop-sdk/traceloop/sdk/client/http.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-04T15:35:30.188Z
Learnt from: nina-kollman
PR: traceloop/openllmetry#3219
File: packages/traceloop-sdk/traceloop/sdk/datasets/dataset.py:357-360
Timestamp: 2025-08-04T15:35:30.188Z
Learning: In the traceloop SDK Dataset class, the established error handling pattern is that HTTP client methods return None on failure (after catching and logging RequestException), and the Dataset API methods check for None return values and raise Exception with descriptive messages. The update_row_api method is inconsistent with this pattern.

Applied to files:

  • packages/traceloop-sdk/traceloop/sdk/dataset/row.py
🧬 Code Graph Analysis (2)
packages/traceloop-sdk/traceloop/sdk/dataset/__init__.py (3)
packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py (1)
  • Dataset (20-112)
packages/traceloop-sdk/traceloop/sdk/dataset/base.py (1)
  • BaseDataset (6-23)
packages/traceloop-sdk/traceloop/sdk/dataset/model.py (1)
  • ColumnType (7-11)
packages/traceloop-sdk/traceloop/sdk/dataset/base.py (1)
packages/traceloop-sdk/traceloop/sdk/client/http.py (1)
  • HTTPClient (7-91)
🪛 Ruff (0.12.2)
packages/traceloop-sdk/traceloop/sdk/dataset/base.py

18-18: Do not perform function call datetime.now in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


19-19: Do not perform function call datetime.now in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Lint
  • GitHub Check: Build Packages (3.11)
🔇 Additional comments (2)
packages/traceloop-sdk/traceloop/sdk/dataset/__init__.py (1)

1-7: Re-exports look good

Clean, focused public surface with explicit all. No issues spotted.

packages/traceloop-sdk/traceloop/sdk/dataset/row.py (1)

40-49: LGTM on update semantics

Consistent with the SDK’s pattern: PUT, raise on failure, then merge values locally.

Comment on lines +15 to +23
def __init__(
self,
http: HTTPClient,
created_at: datetime = datetime.now(),
updated_at: datetime = datetime.now(),
):
self._http = http
self.created_at = created_at
self.updated_at = updated_at
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix frozen timestamps: avoid calling datetime.now() in default args

Defaulting created_at/updated_at to datetime.now() in the signature evaluates once at import time. All instances created without explicit values will share the same timestamps. Ruff B008 flags this.

Apply this diff within the constructor and add the missing Optional typing:

-    def __init__(
-        self,
-        http: HTTPClient,
-        created_at: datetime = datetime.now(),
-        updated_at: datetime = datetime.now(),
-    ):
-        self._http = http
-        self.created_at = created_at
-        self.updated_at = updated_at
+    def __init__(
+        self,
+        http: HTTPClient,
+        created_at: Optional[datetime] = None,
+        updated_at: Optional[datetime] = None,
+    ):
+        self._http = http
+        self.created_at = created_at or datetime.now()
+        self.updated_at = updated_at or datetime.now()

Add this import at the top of the file (outside the selected range):

from typing import Optional
🧰 Tools
🪛 Ruff (0.12.2)

18-18: Do not perform function call datetime.now in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


19-19: Do not perform function call datetime.now in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

🤖 Prompt for AI Agents
In packages/traceloop-sdk/traceloop/sdk/dataset/base.py around lines 15 to 23,
the constructor uses datetime.now() as default argument values causing frozen
timestamps and Ruff B008; change the created_at and updated_at parameters to
Optional[datetime] = None (add "from typing import Optional" at the top of the
file), then inside __init__ set self.created_at = created_at or datetime.now()
and self.updated_at = updated_at or datetime.now(); keep self._http = http
unchanged.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Looks good to me! 👍

Reviewed d852d49 in 47 seconds. Click for details.
  • Reviewed 86 lines of code in 5 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. packages/traceloop-sdk/traceloop/sdk/dataset/base.py:18
  • Draft comment:
    Default parameter values using datetime.now() are evaluated once at definition time, not at each call. This may result in all instances having the same timestamp. Consider using None as the default and assigning datetime.now() inside init if necessary.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
2. packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py:109
  • Draft comment:
    The conditional check on self.rows before appending a new row may be redundant since self.rows is always initialized to an empty list in init.
  • Reason this comment was not posted:
    Confidence changes required: 33% <= threshold 50% None

Workflow ID: wflow_WFlMMIaamDoeRZcr

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/traceloop-sdk/traceloop/sdk/dataset/base.py (1)

15-23: Fix frozen timestamps: avoid datetime.now() in default args (Ruff B008).

Defaulting created_at/updated_at to datetime.now() in the signature evaluates once at import time. All instances constructed without explicit values will share the same timestamps. Move the call(s) inside the constructor and make the params Optional.

Apply this diff within the constructor:

 def __init__(
     self,
     http: HTTPClient,
-    created_at: datetime = datetime.now(),
-    updated_at: datetime = datetime.now(),
+    created_at: Optional[datetime] = None,
+    updated_at: Optional[datetime] = None,
 ):
     self._http = http
-    self.created_at = created_at
-    self.updated_at = updated_at
+    self.created_at = created_at or datetime.now()
+    self.updated_at = updated_at or datetime.now()

And add this import at the top of the file:

from typing import Optional
🧹 Nitpick comments (2)
packages/traceloop-sdk/traceloop/sdk/dataset/base.py (2)

2-2: Consider timezone-aware timestamps.

If these fields are serialized or compared across systems, prefer UTC-aware datetimes to avoid ambiguity.

You can import timezone and use it when setting defaults:

-from datetime import datetime
+from datetime import datetime, timezone

Then inside init:

-    self.created_at = created_at or datetime.now()
-    self.updated_at = updated_at or datetime.now()
+    self.created_at = created_at or datetime.now(timezone.utc)
+    self.updated_at = updated_at or datetime.now(timezone.utc)

6-10: ABC without abstract API: either add an abstract contract or drop ABC.

This class inherits from ABC but defines no abstract methods/properties. If you don’t need abstract enforcement, simplify by removing ABC; otherwise, add an abstract contract (e.g., an id property).

Minimal simplification:

-from abc import ABC
+from abc import ABC  # if you keep ABC, consider adding an abstract API
@@
-class BaseDatasetEntity(ABC):
+class BaseDatasetEntity:
     """
     Abstract base class for dataset-related objects with common attributes
     """

Alternative: define an abstract attribute/method:

from abc import ABC, abstractmethod

class BaseDatasetEntity(ABC):
    @property
    @abstractmethod
    def id(self) -> str: ...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c19ea3 and d852d49.

📒 Files selected for processing (5)
  • packages/traceloop-sdk/traceloop/sdk/dataset/__init__.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/base.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/column.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py (1 hunks)
  • packages/traceloop-sdk/traceloop/sdk/dataset/row.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/traceloop-sdk/traceloop/sdk/dataset/init.py
  • packages/traceloop-sdk/traceloop/sdk/dataset/dataset.py
  • packages/traceloop-sdk/traceloop/sdk/dataset/column.py
  • packages/traceloop-sdk/traceloop/sdk/dataset/row.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/traceloop-sdk/traceloop/sdk/dataset/base.py (1)
packages/traceloop-sdk/traceloop/sdk/client/http.py (1)
  • HTTPClient (7-91)
🪛 Ruff (0.12.2)
packages/traceloop-sdk/traceloop/sdk/dataset/base.py

18-18: Do not perform function call datetime.now in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


19-19: Do not perform function call datetime.now in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Packages (3.11)
  • GitHub Check: Lint

Nina Kollman added 2 commits August 12, 2025 15:10
Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped PR review on 7a4050c because no changed files had a supported extension. If you think this was in error, please contact us and we'll fix it right away.

@nina-kollman nina-kollman merged commit ba68225 into main Aug 12, 2025
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants