@@ -368,3 +368,96 @@ class Table_With_Underscores(dj.Manual):
368368 schema_any (TableNoUnderscores )
369369 with pytest .raises (dj .DataJointError , match = "must be alphanumeric in CamelCase" ):
370370 schema_any (Table_With_Underscores )
371+
372+
373+ class TestSingletonTables :
374+ """Tests for singleton tables (empty primary keys)."""
375+
376+ def test_singleton_declaration (self , schema_any ):
377+ """Singleton table creates correctly with hidden _singleton attribute."""
378+
379+ @schema_any
380+ class Config (dj .Lookup ):
381+ definition = """
382+ # Global configuration
383+ ---
384+ setting : varchar(100)
385+ """
386+
387+ # Access attributes first to trigger lazy loading from database
388+ visible_attrs = Config .heading .attributes
389+ all_attrs = Config .heading ._attributes
390+
391+ # Table should exist and have _singleton as hidden PK
392+ assert "_singleton" in all_attrs
393+ assert "_singleton" not in visible_attrs
394+ assert Config .heading .primary_key == [] # Visible PK is empty for singleton
395+
396+ def test_singleton_insert_and_fetch (self , schema_any ):
397+ """Insert and fetch work without specifying _singleton."""
398+
399+ @schema_any
400+ class Settings (dj .Lookup ):
401+ definition = """
402+ ---
403+ value : int32
404+ """
405+
406+ # Insert without specifying _singleton
407+ Settings .insert1 ({"value" : 42 })
408+
409+ # Fetch should work
410+ result = Settings .fetch1 ()
411+ assert result ["value" ] == 42
412+ assert "_singleton" not in result # Hidden attribute excluded
413+
414+ def test_singleton_uniqueness (self , schema_any ):
415+ """Second insert raises DuplicateError."""
416+
417+ @schema_any
418+ class SingleValue (dj .Lookup ):
419+ definition = """
420+ ---
421+ data : varchar(50)
422+ """
423+
424+ SingleValue .insert1 ({"data" : "first" })
425+
426+ # Second insert should fail
427+ with pytest .raises (dj .errors .DuplicateError ):
428+ SingleValue .insert1 ({"data" : "second" })
429+
430+ def test_singleton_with_multiple_attributes (self , schema_any ):
431+ """Singleton table with multiple secondary attributes."""
432+
433+ @schema_any
434+ class PipelineConfig (dj .Lookup ):
435+ definition = """
436+ # Pipeline configuration singleton
437+ ---
438+ version : varchar(20)
439+ max_workers : int32
440+ debug_mode : bool
441+ """
442+
443+ PipelineConfig .insert1 ({"version" : "1.0.0" , "max_workers" : 4 , "debug_mode" : False })
444+
445+ result = PipelineConfig .fetch1 ()
446+ assert result ["version" ] == "1.0.0"
447+ assert result ["max_workers" ] == 4
448+ assert result ["debug_mode" ] == 0 # bool stored as tinyint
449+
450+ def test_singleton_describe (self , schema_any ):
451+ """Describe should show the singleton nature."""
452+
453+ @schema_any
454+ class Metadata (dj .Lookup ):
455+ definition = """
456+ ---
457+ info : varchar(255)
458+ """
459+
460+ description = Metadata .describe ()
461+ # Description should show just the secondary attribute
462+ assert "info" in description
463+ # _singleton is hidden, implementation detail
0 commit comments