Skip to content

[compat] neptune#4

Merged
asaiacai merged 48 commits into
mainfrom
neptune-compat
Jan 9, 2026
Merged

[compat] neptune#4
asaiacai merged 48 commits into
mainfrom
neptune-compat

Conversation

@asaiacai

@asaiacai asaiacai commented Jan 2, 2026

Copy link
Copy Markdown

add compat monkeypatch package for neptune.

Tested (run the relevant ones):

  • Code formatting: bash format.sh
  • Any manual or new tests for this PR (please specify below)

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @asaiacai, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a crucial compatibility layer to streamline the migration of experiment tracking from Neptune to mlop's Trakkur platform. It allows users to simultaneously log data to both systems during a transition period, ensuring continuity and minimizing disruption to existing workflows. The solution leverages monkeypatching to intercept Neptune API calls, providing a risk-free, gradual migration path without requiring extensive code modifications in user scripts.

Highlights

  • Neptune Compatibility Layer: Introduced a new compatibility layer that enables dual-logging from Neptune to mlop's Trakkur platform with minimal code changes.
  • Zero Code Change Migration: Existing Neptune scripts can enable dual-logging by adding a single import statement, import mlop.compat.neptune, without modifying any other Neptune API calls.
  • Robustness and Safety: The compatibility layer is designed to ensure Neptune functionality is never broken, even if mlop is unavailable or misconfigured. All mlop operations are wrapped in try-except blocks, providing silent fallback to Neptune-only logging.
  • Comprehensive Documentation and Testing: Includes detailed migration guides, example scripts, a dedicated test script, and a comprehensive test suite to ensure the compatibility layer works as expected and facilitates a smooth transition.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/neptune-compat.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive compatibility layer to facilitate migrating from Neptune to mlop, including the core monkeypatching logic, extensive documentation, examples, and tests. The implementation is thorough, but I've identified a critical recursion bug in the wrapper's initialization. My review also includes feedback on a flawed test setup that fails to catch this bug, potential improvements for documentation maintainability, and enhancements to the provided example and test scripts.

Comment thread mlop/compat/neptune.py Outdated
Comment thread mlop/compat/neptune.py Outdated
Comment thread tests/test_neptune_compat.py Outdated
Comment thread NEPTUNE_MIGRATION.md Outdated
Comment thread examples/neptune_migration_README.md Outdated
Comment thread examples/neptune_migration_example.py Outdated
Comment thread mlop/compat/neptune.py Outdated
Comment thread mlop/compat/neptune.py Outdated
Comment thread scripts/test_neptune_migration.sh Outdated
asaiacai and others added 16 commits January 2, 2026 00:07
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…tions

- Fix neptune-scale version check using importlib.metadata instead of __version__
- Run ruff format to fix code formatting issues
- Add type annotations to NeptuneRunWrapper class attributes
- Add type annotation for items list in _flatten_dict method
- Add image logging to test_real_neptune_without_mlop
- Add image logging to test_real_neptune_with_mlop_dual_logging
- Create test images using numpy and PIL
- Log images using Neptune File objects via assign_files() and log_files()
- Validates that image dual-logging works in production CI runs
- Add wait_for_submission() before close() to ensure files are uploaded
- Change debug logging to info/warning for file conversion
- Log successful file conversions and upload counts
- Neptune Scale is async, files weren't uploading before close() was called
…ality

- Neptune-only test: logs 3 grayscale images at steps 0, 1, 2
- Dual-logging test: logs 3 colored images (red/green/blue) at steps 0, 1, 2
- Also includes 1 static image via assign_files()
- Images are 64x64 for better visibility
- Remove `|| echo` fallback patterns from CI workflow that were masking
  test failures
- Change wait_for_submission() to wait_for_processing(verbose=False) to
  prevent "I/O operation on closed file" errors when pytest captures
  stdout/stderr
- Update wrapper's __exit__ method to call wait_for_processing(verbose=False)
  before Neptune's __exit__
- Fix line-too-long errors in logging statements

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The TestNeptuneRealBackend tests access run._mlop_run which is only
available when the monkeypatch is applied. Added 'import mlop.compat.neptune'
before importing Run from neptune_scale to ensure the monkeypatch is
applied first.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Neptune's close() method internally calls wait_for_processing() which
logs status messages even when we call it with verbose=False before.
When pytest captures stdout/stderr, these streams are closed before
Neptune finishes logging, causing "I/O operation on closed file" errors.

This fixture disables Neptune's logger during tests to prevent these errors.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Disable multiple Neptune-related loggers and remove their handlers to
fully prevent I/O errors when pytest captures stdout/stderr.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The -s flag disables pytest's stdout/stderr capture. This prevents
"I/O operation on closed file" errors that occur when Neptune's logger
tries to write after pytest has closed the captured streams.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Configure logging.NullHandler and disable neptune_scale loggers
before importing neptune_scale to prevent any I/O errors from
Neptune's async operations trying to log after streams are closed.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Ubuntu and others added 7 commits January 2, 2026 19:57
When Python's logging handlers fail (e.g., writing to closed streams),
the logging module normally prints error messages to stderr. Setting
raiseExceptions to False suppresses these messages, preventing test
failures caused by Neptune's async operations logging after pytest
closes stdout/stderr.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Set logging.raiseExceptions before any other imports to ensure
Python's logging module doesn't print error messages when handlers
fail, regardless of what other modules might configure.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use int16 for intermediate calculations when adding noise to images,
then clip and convert to uint8. This avoids UFuncOutputCastingError
when adding int16 noise to uint8 arrays.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The mlop CLI is installed via poetry and needs to be invoked with
'poetry run mlop' to be found in the PATH.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change from 'mlop:__main__.main' to 'mlop.__main__:main' to properly
reference the main function in mlop/__main__.py.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add E402 per-file ignore for test_neptune_compat.py (logging must be
  configured before importing neptune_scale to prevent I/O errors)
- Fix import sorting in neptune_migration_example.py
- Apply ruff format to test file

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@asaiacai

asaiacai commented Jan 6, 2026

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive compatibility layer for migrating from Neptune to mlop via dual-logging. The changes include the core monkeypatching logic, extensive documentation, example scripts, and a robust test suite. The implementation is well-designed with a strong focus on safety and ensuring existing Neptune functionality is not disrupted. I've identified a critical issue in an example script that would prevent the feature from working as described, and a medium-severity issue in the wrapper logic related to preserving step information. Once these points are addressed, this will be an excellent addition.

Comment thread examples/neptune_migration_example.py Outdated
Comment thread mlop/compat/neptune.py Outdated
Address 4 outstanding issues from Gemini Code Assist review:

1. CRITICAL: Fix import order in examples
   - Move mlop.compat.neptune import before Neptune imports
   - Add I001 noqa to prevent ruff from reordering
   - Affects: migrated_dual_logging_script, image_logging_example,
     histogram_logging_example
   - Impact: Enables dual-logging functionality (was broken before)

2. MEDIUM: Add missing step parameter in log_histograms
   - Pass step parameter to mlop_run.log() for histograms
   - Makes histogram logging consistent with log_files
   - Location: mlop/compat/neptune.py:422

3. MEDIUM: Fix placeholder image paths in examples
   - Replace 'path/to/image.png' with dynamically created temp images
   - Use tempfile and PIL to create runnable examples
   - Now example can be run without FileNotFoundError

4. MEDIUM: Consolidate documentation
   - Reduce examples/neptune_migration_README.md from 344 to ~107 lines
   - Link to main NEPTUNE_MIGRATION.md for comprehensive docs
   - Prevents documentation drift between files

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Ubuntu and others added 14 commits January 6, 2026 23:49
Update documentation validation checks to match new README structure:
- Replace "Configuration Options" check with "Example Files"
- Replace "Supported Neptune Features" with "What Gets Logged to Both Systems"

The consolidated README links to main NEPTUNE_MIGRATION.md for detailed
configuration, so these section names changed.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Allow users to disable Neptune API calls after Neptune sunset while
keeping mlop logging active. This prevents errors from failed Neptune
requests in post-sunset scenarios.

Features:
- Set DISABLE_NEPTUNE_LOGGING=true to skip all Neptune calls
- All Neptune API methods become no-ops (no errors thrown)
- mlop logging continues normally (mlop-only mode)
- get_run_url() returns "neptune://disabled" placeholder
- One INFO log at startup when Neptune is disabled

Implementation:
- Add _neptune_disabled flag checked in all wrapper methods
- Skip Neptune initialization when flag is set
- Update all 14 wrapper methods to check the flag
- Return sensible defaults (None or placeholders)

Documentation:
- Add "Disabling Neptune (Post-Sunset)" section to main guide
- Add example and usage table
- Update examples README with post-sunset section

Tests:
- Add test_disable_neptune_logging in TestNeptuneCompatFallbackBehavior
- Verify Neptune is not initialized when flag is set
- Verify mlop logging continues working
- Verify placeholder URLs are returned

This provides a graceful kill switch for the Neptune sunset transition.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Comprehensive audit of environment variables used in Neptune compatibility layer:

Environment Variables Documentation:
- Add detailed table with code locations for all 6 env vars
- Add "How Environment Variables Work" section explaining each var
- Document DISABLE_NEPTUNE_LOGGING in main env vars table (was missing)
- Verify MLOP_API_KEY actually works (passes to settings['_auth'])
- Add code references for verification

Neptune Scale Version:
- Document tested version: neptune-scale 0.30.0
- Add compatibility matrix showing Python 3.10/3.11/3.12 tested
- Pin neptune-scale to ">=0.30.0" in pyproject.toml (was "*")

Verified Environment Variables:
✅ MLOP_PROJECT - Required, master switch for dual-logging
✅ MLOP_API_KEY - Optional, falls back to keyring, verified working
✅ MLOP_URL_APP - Optional, for self-hosted instances
✅ MLOP_URL_API - Optional, for self-hosted instances
✅ MLOP_URL_INGEST - Optional, for self-hosted instances
✅ DISABLE_NEPTUNE_LOGGING - Optional, post-sunset kill switch

All env vars verified to actually work in the codebase with code references
provided for audit trail.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The command 'mlop auth status' does not exist. The mlop CLI only has:
- mlop login <token>
- mlop logout

Fixed troubleshooting section to use valid commands:
- Check MLOP_API_KEY environment variable
- Check keyring for stored credentials using Python

This provides actual working debug steps instead of a non-existent command.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Backend PR #15 provides two tags update endpoints:
- HTTP API: POST /api/runs/tags/update (for API key auth)
- tRPC: runs.updateTags (for session auth)

This commit aligns the Python client to use the correct HTTP API endpoint
instead of the tRPC endpoint.

Changes:
- mlop/sets.py: Update URL to /api/runs/tags/update (was /trpc/runs.updateTags)
- mlop/api.py: Use numeric runId, remove projectName from payload
- mlop/iface.py: Update docstring to reflect HTTP API usage
- TAGS_IMPLEMENTATION.md: Document actual implementation and backend integration
- test_tags_unit.py: Add unit tests for payload format verification

The HTTP endpoint is simpler and more appropriate for SDK use:
- No tRPC batch format wrapping needed
- Uses numeric run IDs (no SQID encoding)
- No projectName field required
- Designed for API key authentication

All changes tested and verified to match backend expectations.

Related: https://github.com/Trainy-ai/server-private/pull/15

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The DISABLE_NEPTUNE_LOGGING environment variable was being set by
test_disable_neptune_logging but not cleaned up, causing subsequent
tests to fail when they expected Neptune to be enabled.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update neptune-migration-examples validation to check for:
- 'Usage' instead of 'Example Files'
- 'Supported Features' instead of 'What Gets Logged to Both Systems'

These sections were renamed during documentation consolidation.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Problem**: log_metrics() ignored user's explicit step parameter when
logging to mlop, causing step misalignment during dual-logging.

**Impact**:
- Charts in Neptune vs mlop showed different x-axes
- Violated user intent (explicit step=100 became step=1)
- Broke dual-logging visualization alignment

**Changes**:
1. mlop/compat/neptune.py:
   - Pass step to mlop: `self._mlop_run.log(data, step=step)`
   - Updated docstring to reflect step pass-through behavior
   - Replaced misleading comment about "acceptable step misalignment"

2. tests/test_neptune_compat.py:
   - Fixed assertion to expect step parameter
   - Added test_log_metrics_passes_step_to_mlop() test case
   - Imported 'call' from unittest.mock

3. examples/neptune_migration_README.md:
   - Updated compatibility matrix (step parameter passed through)
   - Added "Step Parameter Handling" documentation section
   - Explained behavior and provided examples

**Testing**:
- ✅ test_dual_logging_metrics_with_env_config - validates step=0 passed
- ✅ test_log_metrics_passes_step_to_mlop - validates step=0, 100, 200
- ✅ All existing Neptune compat tests still pass

**Consistency**:
- log_files() already passed step correctly (line 409)
- log_histograms() already passed step correctly (line 449)
- Now log_metrics() matches this pattern

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@asaiacai

asaiacai commented Jan 8, 2026

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive compatibility layer to facilitate migration from Neptune to mlop by enabling dual-logging. The changes include monkeypatching the Neptune Run class, adding native tag support, and providing extensive documentation, examples, and tests. The implementation is robust, with a strong focus on ensuring that existing Neptune workflows are not disrupted.

My review identified two critical bugs in the NeptuneRunWrapper context manager implementation that occur when Neptune logging is disabled. I have also found several inconsistencies in the documentation regarding step parameter handling and the API used for tag updates, which could lead to user confusion. Additionally, there are minor issues with dependency management in pyproject.toml and some code duplication in the tests. Addressing these points will significantly improve the quality and reliability of this new feature.

Comment thread mlop/compat/neptune.py Outdated
Comment thread mlop/compat/neptune.py
Comment thread CLAUDE.md Outdated
Comment thread examples/neptune_migration_README.md
Comment thread pyproject.toml
Comment thread tests/test_neptune_compat.py Outdated
Ubuntu and others added 8 commits January 8, 2026 02:32
Resolved critical and medium priority issues from Gemini code review:

**Critical Fixes:**
- Fix __enter__ method to handle DISABLE_NEPTUNE_LOGGING (prevents AttributeError)
- Fix __exit__ method to handle DISABLE_NEPTUNE_LOGGING (prevents AttributeError)

**Medium Priority Fixes:**
- Update CLAUDE.md documentation to correct tags API endpoint (HTTP API, not tRPC)
- Remove redundant suppress_neptune_logging fixture (module-level suppression already handles this)
- Move numpy from dev to main dependencies (imported at module level in util.py, file.py, data.py)
- Remove duplicate pillow entry from dev dependencies

**Testing:**
- All Neptune compatibility tests passing
- Format and linting checks passing

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update poetry.lock to reflect numpy being moved from dev to main dependencies.
This is required because numpy is imported at module level in util.py, file.py, and data.py.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace global logging.basicConfig() with mlop-specific logger configuration.

**Problem:**
- Previous docs recommended logging.basicConfig(level=logging.WARNING)
- This configures the ROOT logger globally, affecting ALL loggers
- Can interfere with user's own logging configuration
- Not a Python logging best practice

**Solution:**
- Configure only mlop-specific loggers: 'mlop' and 'mlop.compat.neptune'
- Add console handler setup for pre-init debugging
- Add note explaining when this configuration is needed

**Benefits:**
- No global side effects
- Doesn't interfere with user's logging
- Follows Python logging best practices
- Still works for debugging mlop init failures

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix bug where initial tags from mlop.init(tags=['foo']) were not
  synced to server (tags were set after run creation request)
- Pass tags through OpInit to Op.__init__() so they're included in
  initial create-run request
- Remove unused sqid_encode/sqid_decode functions and sqids dependency
  (_op_id_encoded was set but never read anywhere)

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

Co-Authored-By: Claude <noreply@anthropic.com>
Renamed test_real_dual_logging_integration to test_real_mlop_with_mock_neptune
since it uses mock_neptune_backend fixture (Neptune is mocked, only mlop is real).

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

Co-Authored-By: Claude <noreply@anthropic.com>
- Add POST /api/runs/config/update endpoint support (backend PR #19)
- Add url_update_config to settings
- Add make_compat_update_config_v1() payload function
- Add _update_config() method to ServerInterface
- Add update_config() method to Op for direct mlop usage
- Update Neptune compat log_configs() to sync config to server
- Add tests for update_config() and server sync verification

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

Co-Authored-By: Claude <noreply@anthropic.com>
@asaiacai asaiacai merged commit 9e59b5f into main Jan 9, 2026
17 checks passed
@asaiacai asaiacai deleted the neptune-compat branch January 9, 2026 08:14
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.

1 participant