Skip to content

Commit 4893e3d

Browse files
Merge pull request #1279 from d-v-b/chore/dev-env-fixes
Chore/dev env fixes
2 parents 896e6cd + f45e7c8 commit 4893e3d

File tree

14 files changed

+3695
-226
lines changed

14 files changed

+3695
-226
lines changed

.devcontainer/devcontainer.json

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,7 @@
1-
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2-
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose
31
{
4-
"name": "Existing Docker Compose (Extend)",
5-
// Update the 'dockerComposeFile' list if you have more compose files or use different names.
6-
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
7-
"dockerComposeFile": [
8-
"../docker-compose.yaml",
9-
"docker-compose.yml"
10-
],
11-
// The 'service' property is the name of the service for the container that VS Code should
12-
// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
13-
"service": "app",
14-
// The optional 'workspaceFolder' property is the path VS Code should open by default when
15-
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
16-
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
17-
// Features to add to the dev container. More info: https://containers.dev/features.
18-
// "features": {},
19-
// Use 'forwardPorts' to make a list of ports inside the container available locally.
20-
"forwardPorts": [
21-
80,
22-
443,
23-
3306,
24-
8080,
25-
9000
26-
],
27-
"mounts": [
28-
"type=bind,source=${env:SSH_AUTH_SOCK},target=/ssh-agent"
29-
],
30-
"containerEnv": {
31-
"SSH_AUTH_SOCK": "/ssh-agent"
32-
},
33-
// Uncomment the next line if you want start specific services in your Docker Compose config.
34-
// "runServices": [],
35-
// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
36-
"shutdownAction": "stopCompose",
37-
"onCreateCommand": "python3 -m pip install -q -e .[dev]",
38-
"features": {
39-
"ghcr.io/devcontainers/features/git:1": {},
40-
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
41-
"ghcr.io/devcontainers/features/github-cli:1": {},
42-
},
43-
// Configure tool-specific properties.
44-
"customizations": {
45-
"vscode": {
46-
"extensions": [
47-
"ms-python.python"
48-
]
49-
}
50-
},
51-
"remoteEnv": {
52-
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
53-
}
54-
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
55-
// "remoteUser": "devcontainer"
56-
}
2+
"image": "mcr.microsoft.com/devcontainers/typescript-node:0-18",
3+
"features": {
4+
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
5+
},
6+
"postCreateCommand": "curl -fsSL https://pixi.sh/install.sh | bash && echo 'export PATH=\"$HOME/.pixi/bin:$PATH\"' >> ~/.bashrc"
7+
}

.pre-commit-config.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ repos:
3030
rev: 25.1.0 # matching versions in pyproject.toml and github actions
3131
hooks:
3232
- id: black
33-
args: ["--check", "-v", "src", "tests", "--diff"] # --required-version is conflicting with pre-commit
33+
args: ["-v", "src", "tests", "--diff"] # --required-version is conflicting with pre-commit
3434
- repo: https://github.com/PyCQA/flake8
3535
rev: 7.3.0
3636
hooks:
@@ -50,7 +50,6 @@ repos:
5050
- --max-complexity=62
5151
- --max-line-length=127
5252
- --statistics
53-
- --per-file-ignores=datajoint/diagram.py:C901
5453
files: src/ # a lot of files in tests are not compliant
5554
- repo: https://github.com/rhysd/actionlint
5655
rev: v1.7.7

activate.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#! /usr/bin/bash
2+
# This script registers dot plugins so that we can use graphviz
3+
# to write png images
4+
dot -c

pixi.lock

Lines changed: 3507 additions & 122 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ dependencies = [
2222
"urllib3",
2323
"setuptools",
2424
]
25-
requires-python = ">=3.9,<4.0"
25+
requires-python = ">=3.9,<3.14"
2626
authors = [
2727
{name = "Dimitri Yatsenko", email = "dimitri@datajoint.com"},
2828
{name = "Thinh Nguyen", email = "thinh@datajoint.com"},
@@ -125,7 +125,7 @@ JUPYTER_PASSWORD="datajoint"
125125

126126
[tool.pixi.workspace]
127127
channels = ["conda-forge"]
128-
platforms = ["linux-64"]
128+
platforms = ["linux-64", "osx-arm64", "linux-aarch64"]
129129

130130
[tool.pixi.pypi-dependencies]
131131
datajoint = { path = ".", editable = true }
@@ -138,4 +138,8 @@ test = { features = ["test"], solve-group = "default" }
138138
[tool.pixi.tasks]
139139

140140
[tool.pixi.dependencies]
141+
python = ">=3.9,<3.14"
141142
graphviz = ">=13.1.2,<14"
143+
144+
[tool.pixi.activation]
145+
scripts=["activate.sh"]

src/datajoint/diagram.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
logger = logging.getLogger(__name__.split(".")[0])
2929

3030

31-
if not diagram_active:
31+
if not diagram_active: # noqa: C901
3232

3333
class Diagram:
3434
"""

tests/conftest.py

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,57 @@ def pytest_configure(config):
4545
pass
4646

4747

48+
@pytest.fixture
49+
def clean_autopopulate(experiment, trial, ephys):
50+
"""
51+
Explicit cleanup fixture for autopopulate tests.
52+
53+
Cleans experiment/trial/ephys tables after test completes.
54+
Tests must explicitly request this fixture to get cleanup.
55+
"""
56+
yield
57+
# Cleanup after test - delete in reverse dependency order
58+
ephys.delete()
59+
trial.delete()
60+
experiment.delete()
61+
62+
63+
@pytest.fixture
64+
def clean_jobs(schema_any):
65+
"""
66+
Explicit cleanup fixture for jobs tests.
67+
68+
Cleans jobs table before test runs.
69+
Tests must explicitly request this fixture to get cleanup.
70+
"""
71+
try:
72+
schema_any.jobs.delete()
73+
except DataJointError:
74+
pass
75+
yield
76+
77+
78+
@pytest.fixture
79+
def clean_test_tables(test, test_extra, test_no_extra):
80+
"""
81+
Explicit cleanup fixture for relation tests using test tables.
82+
83+
Ensures test table has lookup data and restores clean state after test.
84+
Tests must explicitly request this fixture to get cleanup.
85+
"""
86+
# Ensure lookup data exists before test
87+
if not test:
88+
test.insert(test.contents, skip_duplicates=True)
89+
90+
yield
91+
92+
# Restore original state after test
93+
test.delete()
94+
test.insert(test.contents, skip_duplicates=True)
95+
test_extra.delete()
96+
test_no_extra.delete()
97+
98+
4899
# Global container registry for cleanup
49100
_active_containers = set()
50101
_docker_client = None
@@ -547,7 +598,7 @@ def mock_cache(tmpdir_factory):
547598
dj.config["cache"] = og_cache
548599

549600

550-
@pytest.fixture
601+
@pytest.fixture(scope="module")
551602
def schema_any(connection_test, prefix):
552603
schema_any = dj.Schema(
553604
prefix + "_test1", schema.LOCALS_ANY, connection=connection_test
@@ -603,6 +654,63 @@ def schema_any(connection_test, prefix):
603654
schema_any.drop()
604655

605656

657+
@pytest.fixture
658+
def schema_any_fresh(connection_test, prefix):
659+
"""Function-scoped schema_any for tests that need fresh schema state."""
660+
schema_any = dj.Schema(
661+
prefix + "_test1_fresh", schema.LOCALS_ANY, connection=connection_test
662+
)
663+
assert schema.LOCALS_ANY, "LOCALS_ANY is empty"
664+
try:
665+
schema_any.jobs.delete()
666+
except DataJointError:
667+
pass
668+
schema_any(schema.TTest)
669+
schema_any(schema.TTest2)
670+
schema_any(schema.TTest3)
671+
schema_any(schema.NullableNumbers)
672+
schema_any(schema.TTestExtra)
673+
schema_any(schema.TTestNoExtra)
674+
schema_any(schema.Auto)
675+
schema_any(schema.User)
676+
schema_any(schema.Subject)
677+
schema_any(schema.Language)
678+
schema_any(schema.Experiment)
679+
schema_any(schema.Trial)
680+
schema_any(schema.Ephys)
681+
schema_any(schema.Image)
682+
schema_any(schema.UberTrash)
683+
schema_any(schema.UnterTrash)
684+
schema_any(schema.SimpleSource)
685+
schema_any(schema.SigIntTable)
686+
schema_any(schema.SigTermTable)
687+
schema_any(schema.DjExceptionName)
688+
schema_any(schema.ErrorClass)
689+
schema_any(schema.DecimalPrimaryKey)
690+
schema_any(schema.IndexRich)
691+
schema_any(schema.ThingA)
692+
schema_any(schema.ThingB)
693+
schema_any(schema.ThingC)
694+
schema_any(schema.ThingD)
695+
schema_any(schema.ThingE)
696+
schema_any(schema.Parent)
697+
schema_any(schema.Child)
698+
schema_any(schema.ComplexParent)
699+
schema_any(schema.ComplexChild)
700+
schema_any(schema.SubjectA)
701+
schema_any(schema.SessionA)
702+
schema_any(schema.SessionStatusA)
703+
schema_any(schema.SessionDateA)
704+
schema_any(schema.Stimulus)
705+
schema_any(schema.Longblob)
706+
yield schema_any
707+
try:
708+
schema_any.jobs.delete()
709+
except DataJointError:
710+
pass
711+
schema_any.drop()
712+
713+
606714
@pytest.fixture
607715
def thing_tables(schema_any):
608716
a = schema.ThingA()
@@ -623,7 +731,7 @@ def thing_tables(schema_any):
623731
yield a, b, c, d, e
624732

625733

626-
@pytest.fixture
734+
@pytest.fixture(scope="module")
627735
def schema_simp(connection_test, prefix):
628736
schema = dj.Schema(
629737
prefix + "_relational", schema_simple.LOCALS_SIMPLE, connection=connection_test
@@ -653,7 +761,7 @@ def schema_simp(connection_test, prefix):
653761
schema.drop()
654762

655763

656-
@pytest.fixture
764+
@pytest.fixture(scope="module")
657765
def schema_adv(connection_test, prefix):
658766
schema = dj.Schema(
659767
prefix + "_advanced",
@@ -694,7 +802,7 @@ def schema_ext(
694802
schema.drop()
695803

696804

697-
@pytest.fixture
805+
@pytest.fixture(scope="module")
698806
def schema_uuid(connection_test, prefix):
699807
schema = dj.Schema(
700808
prefix + "_test1",

tests/test_alter.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515

1616
@pytest.fixture
17-
def schema_alter(connection_test, schema_any):
18-
# Overwrite Experiment and Parent nodes
19-
schema_any(Experiment, context=LOCALS_ALTER)
20-
schema_any(Parent, context=LOCALS_ALTER)
21-
yield schema_any
22-
schema_any.drop()
17+
def schema_alter(connection_test, schema_any_fresh):
18+
# Overwrite Experiment and Parent nodes using fresh schema
19+
schema_any_fresh(Experiment, context=LOCALS_ALTER)
20+
schema_any_fresh(Parent, context=LOCALS_ALTER)
21+
yield schema_any_fresh
22+
schema_any_fresh.drop()
2323

2424

2525
class TestAlter:

tests/test_autopopulate.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import platform
2+
13
import pymysql
24
import pytest
35

@@ -7,7 +9,7 @@
79
from . import schema
810

911

10-
def test_populate(trial, subject, experiment, ephys, channel):
12+
def test_populate(clean_autopopulate, trial, subject, experiment, ephys, channel):
1113
# test simple populate
1214
assert subject, "root tables are empty"
1315
assert not experiment, "table already filled?"
@@ -33,7 +35,7 @@ def test_populate(trial, subject, experiment, ephys, channel):
3335
assert channel
3436

3537

36-
def test_populate_with_success_count(subject, experiment, trial):
38+
def test_populate_with_success_count(clean_autopopulate, subject, experiment, trial):
3739
# test simple populate
3840
assert subject, "root tables are empty"
3941
assert not experiment, "table already filled?"
@@ -51,7 +53,7 @@ def test_populate_with_success_count(subject, experiment, trial):
5153
assert len(trial.key_source & trial) == success_count
5254

5355

54-
def test_populate_key_list(subject, experiment, trial):
56+
def test_populate_key_list(clean_autopopulate, subject, experiment, trial):
5557
# test simple populate
5658
assert subject, "root tables are empty"
5759
assert not experiment, "table already filled?"
@@ -63,7 +65,9 @@ def test_populate_key_list(subject, experiment, trial):
6365
assert n == ret["success_count"]
6466

6567

66-
def test_populate_exclude_error_and_ignore_jobs(schema_any, subject, experiment):
68+
def test_populate_exclude_error_and_ignore_jobs(
69+
clean_autopopulate, schema_any, subject, experiment
70+
):
6771
# test simple populate
6872
assert subject, "root tables are empty"
6973
assert not experiment, "table already filled?"
@@ -79,23 +83,27 @@ def test_populate_exclude_error_and_ignore_jobs(schema_any, subject, experiment)
7983
assert len(experiment.key_source & experiment) == len(experiment.key_source) - 2
8084

8185

82-
def test_allow_direct_insert(subject, experiment):
86+
def test_allow_direct_insert(clean_autopopulate, subject, experiment):
8387
assert subject, "root tables are empty"
8488
key = subject.fetch("KEY", limit=1)[0]
8589
key["experiment_id"] = 1000
8690
key["experiment_date"] = "2018-10-30"
8791
experiment.insert1(key, allow_direct_insert=True)
8892

8993

94+
@pytest.mark.skipif(
95+
platform.system() == "Darwin",
96+
reason="multiprocessing with spawn method (macOS default) cannot pickle thread locks",
97+
)
9098
@pytest.mark.parametrize("processes", [None, 2])
91-
def test_multi_processing(subject, experiment, processes):
99+
def test_multi_processing(clean_autopopulate, subject, experiment, processes):
92100
assert subject, "root tables are empty"
93101
assert not experiment, "table already filled?"
94-
experiment.populate(processes=None)
102+
experiment.populate(processes=processes)
95103
assert len(experiment) == len(subject) * experiment.fake_experiments_per_subject
96104

97105

98-
def test_allow_insert(subject, experiment):
106+
def test_allow_insert(clean_autopopulate, subject, experiment):
99107
assert subject, "root tables are empty"
100108
key = subject.fetch("KEY")[0]
101109
key["experiment_id"] = 1001

tests/test_cascading_delete.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88

99
@pytest.fixture
1010
def schema_simp_pop(schema_simp):
11+
# Clean up tables first to ensure fresh state with module-scoped schema
12+
# Delete in reverse dependency order
13+
Profile().delete()
14+
Website().delete()
15+
G().delete()
16+
E().delete()
17+
D().delete()
18+
B().delete()
19+
L().delete()
20+
A().delete()
21+
1122
A().insert(A.contents, skip_duplicates=True)
1223
L().insert(L.contents, skip_duplicates=True)
1324
B().populate()

0 commit comments

Comments
 (0)