Skip to content

Commit 9c4e313

Browse files
committed
Fix #794: keep legacy database updated with lockfile
1 parent d2d2f1e commit 9c4e313

7 files changed

Lines changed: 24 additions & 17 deletions

File tree

docs/source/reference_guides/configuration.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ are welcome to also support macOS.
4545
````{confval} database_url
4646
4747
SQLite is the legacy state format. pytask now uses `pytask.lock` as the primary state
48-
backend and only consults the database when no lockfile exists. During that first run,
49-
the lockfile is written. Subsequent runs use only the lockfile and do not update the
50-
database state.
48+
backend for change detection. During migration, pytask consults the database when no
49+
lockfile exists and writes `pytask.lock`. For downgrade compatibility, pytask also keeps
50+
the legacy database state updated during builds.
5151
5252
The `database_url` option remains for backwards compatibility and controls the legacy
5353
database location and dialect ([supported by sqlalchemy](https://docs.sqlalchemy.org/en/latest/core/engines.html#backend-specific-urls)).

docs/source/reference_guides/lockfile.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ git-friendly format so runs can be resumed or shared across machines.
55

66
```{note}
77
SQLite is the legacy format. It is still read when no lockfile exists, and a lockfile
8-
is written during that first run. Subsequent runs use only the lockfile and do not
9-
update the database state.
8+
is written during that first run. The lockfile remains the primary backend for skip
9+
decisions, but pytask also keeps the legacy database updated for downgrade
10+
compatibility.
1011
```
1112

1213
## Example

src/_pytask/database.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from sqlalchemy.engine import make_url
99

10-
from _pytask.database_utils import configure_database_if_present
10+
from _pytask.database_utils import create_database
1111
from _pytask.pluginmanager import hookimpl
1212

1313

@@ -45,8 +45,7 @@ def pytask_parse_config(config: dict[str, Any]) -> None:
4545
@hookimpl
4646
def pytask_post_parse(config: dict[str, Any]) -> None:
4747
"""Post-parse the configuration."""
48-
lockfile_path = config["root"] / "pytask.lock"
4948
command = config.get("command")
50-
if lockfile_path.exists() and command in (None, "build"):
49+
if command not in (None, "build"):
5150
return
52-
configure_database_if_present(config["database_url"])
51+
create_database(config["database_url"])

src/_pytask/state.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,4 @@ def update_states(session: Session, task: PTask) -> None:
9191
lockfile_state = _get_lockfile_state(session)
9292
if lockfile_state is not None:
9393
lockfile_state.update_task(session, task)
94-
return
9594
_db_update_states(session, task.signature)

src/_pytask/task_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def wrapper(func: T) -> TaskDecorated[T]:
186186

187187
# When decorator is used without parentheses, call wrapper directly.
188188
if is_task_function(name) and kwargs is None:
189-
return wrapper(cast("T", name)) # ty: ignore[invalid-argument-type]
189+
return wrapper(cast("T", name))
190190
return wrapper
191191

192192

tests/test_database.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ def task_write(path=Path("in.txt"), produces=Path("out.txt")):
5858

5959

6060
def test_rename_database_w_config(tmp_path, runner):
61-
"""Database files are not created when using the lockfile backend."""
61+
"""Database files are created for compatibility with legacy backends."""
6262
path_to_db = tmp_path.joinpath(".db.sqlite")
6363
tmp_path.joinpath("pyproject.toml").write_text(
6464
"[tool.pytask.ini_options]\ndatabase_url='sqlite:///.db.sqlite'"
6565
)
6666
result = runner.invoke(cli, [tmp_path.as_posix()])
6767
assert result.exit_code == ExitCode.OK
68-
assert not path_to_db.exists()
68+
assert path_to_db.exists()
6969

7070

7171
def test_database_url_from_config_is_parsed(tmp_path):
@@ -80,11 +80,11 @@ def test_database_url_from_config_is_parsed(tmp_path):
8080

8181

8282
def test_rename_database_w_cli(tmp_path, runner):
83-
"""Database files are not created when using the lockfile backend."""
83+
"""Database files are created for compatibility with legacy backends."""
8484
path_to_db = tmp_path.joinpath(".db.sqlite")
8585
result = runner.invoke(
8686
cli,
8787
["--database-url", "sqlite:///.db.sqlite", tmp_path.as_posix()],
8888
)
8989
assert result.exit_code == ExitCode.OK
90-
assert not path_to_db.exists()
90+
assert path_to_db.exists()

tests/test_lockfile.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
from _pytask.lockfile import read_lockfile
1212
from _pytask.models import NodeInfo
1313
from _pytask.nodes import PythonNode
14+
from pytask import DatabaseSession
1415
from pytask import ExitCode
1516
from pytask import PathNode
17+
from pytask import State
1618
from pytask import TaskWithoutPath
1719
from pytask import build
1820

@@ -81,7 +83,7 @@ def test_python_node_id_is_collision_free(tmp_path):
8183
assert left_id != right_id
8284

8385

84-
def test_lockfile_does_not_write_state_to_database(tmp_path):
86+
def test_lockfile_writes_state_to_database_for_compatibility(tmp_path):
8587
def func(path):
8688
path.write_text("data")
8789

@@ -96,7 +98,13 @@ def func(path):
9698
assert (tmp_path / "pytask.lock").exists()
9799

98100
db_path = tmp_path / ".pytask" / "pytask.sqlite3"
99-
assert not db_path.exists()
101+
assert db_path.exists()
102+
103+
task_signature = session.tasks[0].signature
104+
with DatabaseSession() as db_session:
105+
state = db_session.get(State, (task_signature, task_signature))
106+
assert state is not None
107+
assert state.hash_ == session.tasks[0].state()
100108

101109

102110
def test_clean_lockfile_removes_stale_entries(tmp_path):

0 commit comments

Comments
 (0)