Skip to content

Commit f824faa

Browse files
feat: dj.Entry, dj.Ingest and dj.Compute as tier names on one axis (#1558)
The populated tiers sit on inconsistent axes. `Manual` names the writer but means the origin; `Imported` names the origin but means the writer; `Computed` names the result. A reader reasoning from the names alone lands in the wrong tier, and the recognizable outcome is an `Imported` table with no make() and a permanent allow_direct_insert=True papering over a modeling error. The new names put all of them on one question -- what puts rows in this table -- and the grammar carries it: nouns are tables something else fills, verbs are precisely the two things populate() does. Lookup noun the code, via contents Entry noun a writer outside the table Ingest verb the table's own make(), reading an external source Compute verb the table's own make(), deriving from other tables These are assignments, not subclasses, so a table declared either way is the same class: same SQL prefix, same Role, same tier detection, same DDL. That is not a property to be maintained but one that cannot be broken. This is the 2.3.4 half of #1546: the names become available and nothing else changes. Defaults, repr, diagram labels and what the documentation teaches stay on the old names until 2.4. `Manual`, `Imported` and `Computed` are permanent aliases -- no deprecation, no migration, no broken tutorials or third-party code, ever.
1 parent f80d7aa commit f824faa

3 files changed

Lines changed: 97 additions & 1 deletion

File tree

‎src/datajoint/__init__.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@
3333
"AutoPopulate",
3434
"Job",
3535
"Manual",
36+
"Entry",
3637
"Lookup",
3738
"Imported",
39+
"Ingest",
3840
"Computed",
41+
"Compute",
3942
"Part",
4043
"Not",
4144
"AndList",
@@ -95,7 +98,7 @@
9598
from .autopopulate import AutoPopulate
9699
from .jobs import Job
97100
from .table import FreeTable as _FreeTable, Table, ValidationResult
98-
from .user_tables import Computed, Imported, Lookup, Manual, Part
101+
from .user_tables import Compute, Computed, Entry, Imported, Ingest, Lookup, Manual, Part
99102
from .version import __version__
100103

101104
# =============================================================================

‎src/datajoint/user_tables.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,19 @@ def alter(self, prompt=True, context=None):
262262
super().alter(prompt=prompt, context=context or self.declaration_context)
263263

264264

265+
#: Tier names on a single axis -- what puts rows in the table. ``Manual`` names
266+
#: its writer but means the origin; ``Imported`` names the origin but means the
267+
#: writer. ``Entry``, ``Ingest`` and ``Compute`` put all of them on the one
268+
#: question, and the grammar carries it: nouns are tables something else fills,
269+
#: verbs are the two things ``populate()`` does.
270+
#:
271+
#: These are the same classes, not subclasses, so a table declared either way is
272+
#: identical -- same SQL prefix, same ``Role``, same tier detection. The old
273+
#: names are permanent aliases, never deprecated. See #1546.
274+
Entry = Manual
275+
Ingest = Imported
276+
Compute = Computed
277+
265278
user_table_classes = (Manual, Lookup, Computed, Imported, Part)
266279

267280

‎tests/unit/test_tier_aliases.py‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""`Entry`/`Ingest`/`Compute` are the old tiers under names on one axis.
2+
3+
#1546: `Manual` names its writer but means the origin; `Imported` names the
4+
origin but means the writer. The new names put all of them on one question --
5+
what puts rows in this table -- and the old names stay permanently.
6+
7+
These are aliases, not subclasses, so "identical table" is not a property to be
8+
maintained but one that cannot be broken. These tests pin that, plus the
9+
transparency checks the issue asks for: SQL prefix, Role, tier detection, and
10+
`lookup_class_name`.
11+
"""
12+
13+
import pytest
14+
15+
import datajoint as dj
16+
from datajoint.settings import Role, role_to_prefix
17+
from datajoint.user_tables import _get_tier
18+
19+
PAIRS = [
20+
("Entry", "Manual", Role.manual),
21+
("Ingest", "Imported", Role.imported),
22+
("Compute", "Computed", Role.computed),
23+
]
24+
25+
26+
@pytest.mark.parametrize("new, old, _role", PAIRS)
27+
def test_alias_is_the_same_class(new, old, _role):
28+
"""Not a subclass: the same object, so nothing can drift between them."""
29+
assert getattr(dj, new) is getattr(dj, old)
30+
31+
32+
@pytest.mark.parametrize("new, old, role", PAIRS)
33+
def test_prefix_matches_the_role(new, old, role):
34+
assert getattr(dj, new)._prefix == role_to_prefix[role]
35+
36+
37+
@pytest.mark.parametrize("new, old, _role", PAIRS)
38+
def test_exported_from_the_package(new, old, _role):
39+
assert new in dj.__all__
40+
assert old in dj.__all__
41+
42+
43+
def test_lookup_and_part_are_unchanged():
44+
"""The issue renames three tiers; these two already sit on the axis."""
45+
assert not hasattr(dj, "LookupAlias")
46+
assert dj.Lookup._prefix == "#"
47+
assert dj.Part.__name__ == "Part"
48+
49+
50+
@pytest.mark.parametrize(
51+
"table_name, expected",
52+
[
53+
("subject", "Manual"),
54+
("#param", "Lookup"),
55+
("_ingest", "Imported"),
56+
("__analysis", "Computed"),
57+
("subject__detail", "Part"),
58+
],
59+
)
60+
def test_tier_detection_resolves_to_one_class(table_name, expected):
61+
"""Tier detection is by SQL prefix, so the alias cannot confuse it."""
62+
tier = _get_tier(f"`lab`.`{table_name}`")
63+
assert tier is not None and tier.__name__ == expected
64+
65+
66+
def test_declaring_either_way_produces_the_same_table_name():
67+
"""`_prefix` plus the class name is the whole of the table name."""
68+
from datajoint.utils import from_camel_case
69+
70+
class ViaOld(dj.Manual):
71+
definition = ""
72+
73+
class ViaNew(dj.Entry):
74+
definition = ""
75+
76+
assert ViaOld._prefix == ViaNew._prefix
77+
assert from_camel_case("Subject") == "subject"
78+
# Both inherit the identical metaclass machinery, so the computed name for a
79+
# given class name is the same by construction.
80+
assert type(ViaOld) is type(ViaNew)

0 commit comments

Comments
 (0)