diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 629b0d5c..4dd79630 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -65,7 +65,7 @@ repos: optree, pluggy, rich, - sqlalchemy, + sqlalchemy>2, types-setuptools, ] pass_filenames: false diff --git a/docs/source/changes.md b/docs/source/changes.md index 9bd779ef..b03b8898 100644 --- a/docs/source/changes.md +++ b/docs/source/changes.md @@ -5,7 +5,11 @@ chronological order. Releases follow [semantic versioning](https://semver.org/) releases are available on [PyPI](https://pypi.org/project/pytask) and [Anaconda.org](https://anaconda.org/conda-forge/pytask). -## 0.4.5 - 2023-12-xx +## 0.5.0 - 2024-xx-xx + +- {pull}`544` requires sqlalchemy `>=2` and upgrades the syntax. + +## 0.4.5 - 2024-01-xx - {pull}`515` enables tests with graphviz in CI. Thanks to {user}`NickCrews`. - {pull}`517` raises an error when the configuration file contains a non-existing path diff --git a/environment.yml b/environment.yml index 35943e32..7074be72 100644 --- a/environment.yml +++ b/environment.yml @@ -18,7 +18,7 @@ dependencies: - pluggy >=1.0.0 - optree >=0.9 - rich - - sqlalchemy >=1.4.36 + - sqlalchemy >=2 - tomli >=1.0.0 - typing_extensions - universal_pathlib diff --git a/pyproject.toml b/pyproject.toml index 987b39fe..4a42ab3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "packaging", "pluggy>=1", "rich", - "sqlalchemy>=1.4.36", + "sqlalchemy>=2", 'tomli>=1; python_version < "3.11"', 'typing-extensions; python_version < "3.9"', ] diff --git a/src/_pytask/database_utils.py b/src/_pytask/database_utils.py index 95a22344..1e26b9f8 100644 --- a/src/_pytask/database_utils.py +++ b/src/_pytask/database_utils.py @@ -4,10 +4,10 @@ from typing import TYPE_CHECKING from _pytask.dag_utils import node_and_neighbors -from sqlalchemy import Column from sqlalchemy import create_engine -from sqlalchemy import String -from sqlalchemy.orm import declarative_base +from sqlalchemy.orm import DeclarativeBase +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column from sqlalchemy.orm import sessionmaker if TYPE_CHECKING: @@ -27,17 +27,18 @@ DatabaseSession = sessionmaker() -BaseTable = declarative_base() +class BaseTable(DeclarativeBase): + pass -class State(BaseTable): # type: ignore[valid-type, misc] +class State(BaseTable): """Represent the state of a node in relation to a task.""" __tablename__ = "state" - task = Column(String, primary_key=True) - node = Column(String, primary_key=True) - hash_ = Column(String) + task: Mapped[str] = mapped_column(primary_key=True) + node: Mapped[str] = mapped_column(primary_key=True) + hash_: Mapped[str] def create_database(url: str) -> None: @@ -54,7 +55,7 @@ def _create_or_update_state(first_key: str, second_key: str, hash_: str) -> None if not state_in_db: session.add(State(task=first_key, node=second_key, hash_=hash_)) else: - state_in_db.hash_ = hash_ # type: ignore[assignment] + state_in_db.hash_ = hash_ session.commit() diff --git a/src/_pytask/profile.py b/src/_pytask/profile.py index 3147042a..9d9ba119 100644 --- a/src/_pytask/profile.py +++ b/src/_pytask/profile.py @@ -29,10 +29,8 @@ from _pytask.session import Session from _pytask.traceback import Traceback from rich.table import Table -from sqlalchemy import Column -from sqlalchemy import Float -from sqlalchemy import String - +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column if TYPE_CHECKING: from _pytask.reports import ExecutionReport @@ -51,9 +49,9 @@ class Runtime(BaseTable): __tablename__ = "runtime" - task = Column(String, primary_key=True) - date = Column(Float) - duration = Column(Float) + task: Mapped[str] = mapped_column(primary_key=True) + date: Mapped[float] + duration: Mapped[float] @hookimpl(tryfirst=True) @@ -198,7 +196,7 @@ def _collect_runtimes(tasks: list[PTask]) -> dict[str, float]: """Collect runtimes.""" with DatabaseSession() as session: runtimes = [session.get(Runtime, task.signature) for task in tasks] - return {task.name: r.duration for task, r in zip(tasks, runtimes) if r} # type: ignore[misc] + return {task.name: r.duration for task, r in zip(tasks, runtimes) if r} class FileSizeNameSpace: