@@ -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`
1616import datajoint as dj
1717
1818dj.config.safemode = False
19- dj.conn(host = " localhost " , user = " u " , password = " p " )
19+ dj.conn() # Triggers singleton creation, returns connection
2020schema = dj.Schema(" my_schema" )
2121
2222@schema
2323class 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
3237import 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" )
5762inst.config # Config instance
5863inst.connection # Connection instance
5964inst.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
96101dj.conn() # ThreadSafetyError
97102dj.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
100105inst.config.safemode = False # OK
101106inst.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
130136import 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
191189def _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
212219def conn ():
213220 return _get_singleton().connection
214221
222+ # dj.Schema() -> singleton.Schema()
215223def 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