Skip to content

Commit 32b5235

Browse files
docs: Consolidate to single singleton instance, dj.Instance()
- dj.Instance() (uppercase) for consistency with dj.Schema() - Single _singleton_instance created lazily - dj.config -> _singleton.config (via proxy) - dj.conn() -> _singleton.connection - dj.Schema() -> _singleton.Schema() - dj.FreeTable() -> _singleton.FreeTable() - All trigger same singleton creation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6fe7497 commit 32b5235

File tree

1 file changed

+52
-40
lines changed

1 file changed

+52
-40
lines changed

docs/design/thread-safe-mode.md

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ DataJoint uses global state (`dj.config`, `dj.conn()`) that is not thread-safe.
66

77
## Solution
88

9-
Introduce **instance** objects that encapsulate config and connection. The `dj` module provides access to a lazily-loaded singleton instance. New isolated instances are created with `dj.instance()`.
9+
Introduce **Instance** objects that encapsulate config and connection. The `dj` module provides access to a lazily-loaded singleton instance. New isolated instances are created with `dj.Instance()`.
1010

1111
## API
1212

@@ -16,22 +16,27 @@ Introduce **instance** objects that encapsulate config and connection. The `dj`
1616
import datajoint as dj
1717

1818
dj.config.safemode = False
19-
dj.conn(host="localhost", user="u", password="p")
19+
dj.conn() # Triggers singleton creation, returns connection
2020
schema = dj.Schema("my_schema")
2121

2222
@schema
2323
class Mouse(dj.Manual):
2424
definition = "..."
2525
```
2626

27-
Internally, `dj.config`, `dj.conn()`, and `dj.Schema()` delegate to a lazily-loaded singleton instance.
27+
Internally, `dj.config`, `dj.conn()`, and `dj.Schema()` are aliases to the singleton instance:
28+
- `dj.config``dj._singleton_instance.config`
29+
- `dj.conn()``dj._singleton_instance.connection`
30+
- `dj.Schema()``dj._singleton_instance.Schema()`
31+
32+
The singleton is created lazily on first access to any of these.
2833

2934
### New API (isolated instance)
3035

3136
```python
3237
import datajoint as dj
3338

34-
inst = dj.instance(
39+
inst = dj.Instance(
3540
host="localhost",
3641
user="user",
3742
password="password",
@@ -53,7 +58,7 @@ Each instance has:
5358
- `inst.FreeTable()` - FreeTable factory using instance's connection
5459

5560
```python
56-
inst = dj.instance(host="localhost", user="u", password="p")
61+
inst = dj.Instance(host="localhost", user="u", password="p")
5762
inst.config # Config instance
5863
inst.connection # Connection instance
5964
inst.Schema("name") # Creates schema using inst.connection
@@ -87,7 +92,7 @@ When `thread_safe=True`, accessing the singleton raises `ThreadSafetyError`:
8792
- `dj.config` raises `ThreadSafetyError`
8893
- `dj.conn()` raises `ThreadSafetyError`
8994
- `dj.Schema()` raises `ThreadSafetyError`
90-
- `dj.instance()` works - isolated instances are always allowed
95+
- `dj.Instance()` works - isolated instances are always allowed
9196

9297
```python
9398
# thread_safe=True
@@ -96,7 +101,7 @@ dj.config # ThreadSafetyError
96101
dj.conn() # ThreadSafetyError
97102
dj.Schema("name") # ThreadSafetyError
98103

99-
inst = dj.instance(host="h", user="u", password="p") # OK
104+
inst = dj.Instance(host="h", user="u", password="p") # OK
100105
inst.config.safemode = False # OK
101106
inst.Schema("name") # OK
102107
```
@@ -105,32 +110,33 @@ inst.Schema("name") # OK
105110

106111
| Operation | `thread_safe=False` | `thread_safe=True` |
107112
|-----------|--------------------|--------------------|
108-
| `dj.config` | Singleton config | `ThreadSafetyError` |
109-
| `dj.conn()` | Singleton connection | `ThreadSafetyError` |
110-
| `dj.Schema()` | Uses singleton | `ThreadSafetyError` |
111-
| `dj.instance()` | Works | Works |
113+
| `dj.config` | `_singleton.config` | `ThreadSafetyError` |
114+
| `dj.conn()` | `_singleton.connection` | `ThreadSafetyError` |
115+
| `dj.Schema()` | `_singleton.Schema()` | `ThreadSafetyError` |
116+
| `dj.Instance()` | Works | Works |
112117
| `inst.config` | Works | Works |
113118
| `inst.connection` | Works | Works |
114119
| `inst.Schema()` | Works | Works |
115120

116121
## Singleton Lazy Loading
117122

118-
The singleton instance is created lazily on first access to `dj.config`, `dj.conn()`, or `dj.Schema()`:
123+
The singleton instance is created lazily on first access:
119124

120125
```python
121-
# First access triggers singleton creation
122-
dj.config.safemode # Creates singleton, returns singleton.config.safemode
123-
dj.conn() # Returns singleton.connection (connects if needed)
124-
dj.Schema("name") # Returns singleton.Schema("name")
126+
dj.config # Creates singleton, returns _singleton.config
127+
dj.conn() # Creates singleton, returns _singleton.connection
128+
dj.Schema("name") # Creates singleton, returns _singleton.Schema("name")
125129
```
126130

131+
All three trigger creation of the same singleton instance.
132+
127133
## Usage Example
128134

129135
```python
130136
import datajoint as dj
131137

132138
# Create isolated instance
133-
inst = dj.instance(
139+
inst = dj.Instance(
134140
host="localhost",
135141
user="user",
136142
password="password",
@@ -173,47 +179,53 @@ class Instance:
173179
return FreeTable(self.connection, full_table_name)
174180
```
175181

176-
### 2. Add dj.instance()
177-
178-
```python
179-
def instance(host, user, password, **kwargs) -> Instance:
180-
"""Create a new isolated instance with its own config and connection."""
181-
return Instance(host, user, password, **kwargs)
182-
```
183-
184-
### 3. Singleton with lazy loading
182+
### 2. Singleton with lazy loading
185183

186184
```python
187185
# Module level
188186
_thread_safe = _load_thread_safe_from_env_or_config()
189-
_singleton = None
187+
_singleton_instance = None
190188

191189
def _get_singleton():
192190
if _thread_safe:
193191
raise ThreadSafetyError(
194192
"Global DataJoint state is disabled in thread-safe mode. "
195-
"Use dj.instance() to create an isolated instance."
193+
"Use dj.Instance() to create an isolated instance."
196194
)
197-
global _singleton
198-
if _singleton is None:
199-
_singleton = Instance(
200-
host=_load_from_config("database.host"),
201-
user=_load_from_config("database.user"),
202-
password=_load_from_config("database.password"),
195+
global _singleton_instance
196+
if _singleton_instance is None:
197+
_singleton_instance = Instance(
198+
host=_load_from_env_or_config("database.host"),
199+
user=_load_from_env_or_config("database.user"),
200+
password=_load_from_env_or_config("database.password"),
203201
...
204202
)
205-
return _singleton
203+
return _singleton_instance
204+
```
205+
206+
### 3. Legacy API as aliases
206207

207-
# Public API
208-
@property
209-
def config():
210-
return _get_singleton().config
208+
```python
209+
# dj.config -> singleton.config
210+
class _ConfigProxy:
211+
def __getattr__(self, name):
212+
return getattr(_get_singleton().config, name)
213+
def __setattr__(self, name, value):
214+
setattr(_get_singleton().config, name, value)
211215

216+
config = _ConfigProxy()
217+
218+
# dj.conn() -> singleton.connection
212219
def conn():
213220
return _get_singleton().connection
214221

222+
# dj.Schema() -> singleton.Schema()
215223
def Schema(name, **kwargs):
216224
return _get_singleton().Schema(name, **kwargs)
225+
226+
# dj.FreeTable() -> singleton.FreeTable()
227+
def FreeTable(full_table_name):
228+
return _get_singleton().FreeTable(full_table_name)
217229
```
218230

219231
### 4. Refactor internal code
@@ -224,4 +236,4 @@ All internal code uses `self.connection._config` instead of global `config`:
224236

225237
## Error Messages
226238

227-
- Singleton access: `"Global DataJoint state is disabled in thread-safe mode. Use dj.instance() to create an isolated instance."`
239+
- Singleton access: `"Global DataJoint state is disabled in thread-safe mode. Use dj.Instance() to create an isolated instance."`

0 commit comments

Comments
 (0)