feat: Thread-safe mode for multi-tenant applications#17
Closed
dimitri-yatsenko wants to merge 13 commits intomasterfrom
Closed
feat: Thread-safe mode for multi-tenant applications#17dimitri-yatsenko wants to merge 13 commits intomasterfrom
dimitri-yatsenko wants to merge 13 commits intomasterfrom
Conversation
This adds opt-in thread-safe mode that blocks global state access,
requiring explicit connection management for multi-tenant web apps.
Changes:
- Add ThreadSafetyError exception
- Add thread_safe setting (default False, can be set via DJ_THREAD_SAFE env)
- Guard config dict access (__getitem__, __setitem__) when thread_safe=True
- Guard dj.conn() singleton when thread_safe=True
- Add Connection.from_config() class method for explicit connection creation
- Add backend parameter to Connection.__init__
- Convert config dict access to attribute access in Connection methods
When thread_safe=True:
- dj.config["..."] raises ThreadSafetyError
- dj.conn() raises ThreadSafetyError
- Connection.from_config() always works (reads config at call time)
Usage:
dj.config.thread_safe = True # or DJ_THREAD_SAFE=true
conn = dj.Connection.from_config(tenant_config)
schema = dj.Schema("mydb", connection=conn)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- thread_safe is a regular setting (env var, config file, programmatic) - Once True, cannot be set back to False (one-way lock) - When enabled, all global config access blocked except thread_safe itself - Allow Pydantic model_ methods during initialization - Tests use object.__setattr__ to bypass lock for reset Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Design document covering: - One-way lock for thread_safe setting - Only thread_safe accessible on dj.config in thread-safe mode - ConnectionConfig with read/write access, forwarding to global when off - Universal API (Connection.from_config + conn.config) works in both modes - Migration path and backward compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This setting was defined but never read by any code - dead code. Removed from: - Config class definition - Template generation - Test that used it (test still validates error suppression) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Database charset/encoding should be configured on the server, not the client. Removes: - charset from ConnectionSettings - charset from connection template - charset parameter from adapter connect calls - charset from thread-safe mode spec Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ConnectionConfig class with read/write settings - Connection.config forwards to global when thread_safe=False - Connection.config uses defaults when thread_safe=True - Connection.from_config() accepts all connection-scoped settings - Schema.create_tables property defers to connection.config - Schema requires explicit connection in thread-safe mode - Add create_tables to ConnectionConfig - Update spec with access patterns (schema.connection.config) - Add comprehensive tests for all components Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- thread_safe is now read-only after initialization - Can only be set via DJ_THREAD_SAFE env var or datajoint.json - Programmatic setting raises ThreadSafetyError - ConnectionConfig now uses _use_global_fallback instead of _thread_safe - New API (from_config) uses defaults, never falls back to global - Legacy API (dj.conn) falls back to global for backward compat - Behavior is based on which API was used, not thread_safe flag - Updated tests and spec document Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updated documentation to reflect that thread_safe is read-only after initialization and can only be set via DJ_THREAD_SAFE environment variable or datajoint.json config file, not programmatically. Removed references to "one-way lock" pattern which is no longer accurate. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added `conn.config.override()` method to temporarily change connection-
scoped settings. This provides the same functionality as `dj.config.override()`
but works in thread-safe mode where global config access is blocked.
Example:
with conn.config.override(safemode=False, display_limit=50):
# settings changed temporarily
pass
# settings restored
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed from object.__setattr__() to super().__setattr__() so that Pydantic's validate_assignment=True works correctly. This fixes: - test_loglevel_validation - now correctly rejects invalid loglevels - test_fetch_format_validation - now correctly rejects invalid formats - test_cache_path_string - now correctly coerces strings to Path Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed init_function from ConnectionSettings and all connection code paths. This was dead code - a pass-through to PyMySQL's init_command that was never used by DataJoint. Removed from: - ConnectionSettings class (now removed entirely) - Config.connection field - Template generation - dj.conn() function - Connection.__init__ - Connection.from_config - MySQL adapter - Thread-safe mode spec Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Renamed ConnectionConfig to ConnectionSettings - Removed duplicate _DEFAULTS dict - Added _get_default() to read defaults from Config Pydantic field definitions - Changed constructor signature: ConnectionSettings(values=dict, use_global_fallback=bool) - Updated tests to use new constructor pattern This eliminates duplication of setting definitions, validation, and defaults between Config and ConnectionSettings classes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Owner
Author
|
closing for a more elegant solution |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds opt-in thread-safe mode that blocks global state access, requiring explicit connection management for multi-tenant web applications and concurrent processing.
📄 Design Specification
Key Features
Thread-Safe Mode (
thread_safesetting)DJ_THREAD_SAFEenvironment variable ordatajoint.jsonconfig filedj.configaccess except readingthread_safeitselfdj.conn()singleton to prevent shared connection stateNew Connection API
Connection.from_config(): Creates connections with explicit configurationthread_safeis on or offConnection-Scoped Configuration (
conn.config)ConnectionConfigclass for per-connection settingssafemode,database_prefix,stores,cache,display_limit, etc.conn.config.override(): Context manager for temporary setting changesdj.conn()) falls back to global config for backward compatibilityUsage
Breaking Changes
None. Thread-safe mode is opt-in and disabled by default. Existing code continues to work unchanged.
Other Fixes
Config.__setattr__to preserve Pydantic'svalidate_assignment=Trueenable_python_native_blobssetting (was defined but never read)charsetsetting: Database charset should be configured on the server, not clientFiles Changed
src/datajoint/settings.py- Thread-safe guards, read-onlythread_safesrc/datajoint/connection.py-ConnectionConfig,from_config(),override()src/datajoint/schema.py- Require explicit connection in thread-safe modesrc/datajoint/errors.py-ThreadSafetyErrorexceptiontests/unit/test_thread_safe.py- Comprehensive test suitetests/unit/test_settings.py- Updated tests for new behaviordocs/design/thread-safe-mode.md- Design specificationTest Plan
🤖 Generated with Claude Code