Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 26 additions & 30 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
contents: write
strategy:
matrix:
py_version: ["3.9", "3.10", "3.11", "3.12"]
py_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
runs-on: ubuntu-latest
env:
NEO4J_MIGRATIONS_USER: neo4j
Expand All @@ -20,19 +20,18 @@ jobs:
NEO4J_MIGRATIONS_SCHEME: neo4j
steps:
- uses: actions/checkout@v2
- name: Install poetry
run: pipx install poetry
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: "${{ matrix.py_version }}"
cache: "poetry"
- name: Install deps
run: poetry install
- name: Install the project
run: uv sync --locked --all-extras --dev
- name: Run pytest check
run: poetry run pytest -vv --cov="neo4j_python_migrations" .
run: uv run pytest -vv --cov="neo4j_python_migrations" .
- name: Generate report
run: poetry run coverage xml
run: uv run coverage xml
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v3
if: matrix.py_version == '3.9'
Expand All @@ -58,17 +57,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install poetry
run: pipx install poetry
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "poetry"
- name: Install deps
run: poetry install
- name: Install the project
run: uv sync --locked --all-extras --dev
- name: Run flake8
run: poetry run flake8 neo4j_python_migrations --count
run: uv run flake8 neo4j_python_migrations --count
black:
permissions:
checks: write
Expand All @@ -77,17 +75,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install poetry
run: pipx install poetry
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "poetry"
- name: Install deps
run: poetry install
- name: Install the project
run: uv sync --locked --all-extras --dev
- name: Run black
run: poetry run black --check neo4j_python_migrations tests
run: uv run black --check neo4j_python_migrations tests

mypy:
permissions:
Expand All @@ -97,14 +94,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install poetry
run: pipx install poetry
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "poetry"
- name: Install deps
run: poetry install
- name: Install the project
run: uv sync --locked --all-extras --dev
- name: Run mypy
run: poetry run mypy neo4j_python_migrations tests
run: uv run mypy neo4j_python_migrations tests
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Make sure to follow the naming convention as stated in
### Cypher
Just create a Cypher file with your custom script, for example `./migrations/V0001__initial.cypher`:
```
CREATE CONSTRAINT UniqueAuthor IF NOT EXISTS ON (a:AUTHOR) ASSERT a.uuid IS UNIQUE;
CREATE CONSTRAINT UniqueAuthor IF NOT EXISTS FOR (a:AUTHOR) REQUIRE a.uuid IS UNIQUE;
CREATE INDEX author_uuid_index IF NOT EXISTS FOR (a:AUTHOR) ON (a.uuid);
```
This script will be executed within a single transaction.
Expand All @@ -57,7 +57,7 @@ from neo4j import Transaction

# This function must be present
def up(tx: Transaction):
tx.run("DROP INDEX author_uuid_index")
tx.run("DROP CONSTRAINT UniqueAuthor")
```

## Applying migrations
Expand Down
16 changes: 8 additions & 8 deletions neo4j_python_migrations/analyzer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import enum
from dataclasses import dataclass, field
from itertools import chain
from typing import List, Optional
from typing import Optional

from attr import define, field
from packaging.version import Version

from neo4j_python_migrations.migration import Migration
Expand All @@ -24,7 +24,7 @@ class InvalidVersionStatus(enum.Enum):
DIFFERENT = enum.auto()


@define
@dataclass
class InvalidVersion:
"""
A class for storing an invalid version.
Expand All @@ -36,18 +36,18 @@ class InvalidVersion:
status: InvalidVersionStatus


@define
@dataclass
class AnalyzingResult:
"""A class for storing the analysis result."""

latest_applied_version: Optional[str] = None
pending_migrations: List[Migration] = field(factory=lambda: []) # noqa: WPS522
invalid_versions: List[InvalidVersion] = field(factory=lambda: []) # noqa: WPS522
pending_migrations: list[Migration] = field(default_factory=list)
invalid_versions: list[InvalidVersion] = field(default_factory=list)


def analyze( # noqa: WPS210
local_migrations: List[Migration],
remote_migrations: List[Migration],
local_migrations: list[Migration],
remote_migrations: list[Migration],
) -> AnalyzingResult:
"""
Analyze local and remote migrations.
Expand Down
4 changes: 2 additions & 2 deletions neo4j_python_migrations/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Optional

from attr import define
from neo4j import GraphDatabase
from typer import Exit, Option, Typer
from yarl import URL
Expand All @@ -12,7 +12,7 @@
cli = Typer()


@define
@dataclass
class State:
"""Storage of common options for commands."""

Expand Down
8 changes: 5 additions & 3 deletions neo4j_python_migrations/dao.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from functools import cached_property
from getpass import getuser
from typing import List, Optional
from typing import Optional

from neo4j import Driver

Expand Down Expand Up @@ -151,7 +151,7 @@ def add_migration(
"Check the migration graph.",
)

def get_applied_migrations(self) -> List[Migration]:
def get_applied_migrations(self) -> list[Migration]:
"""
Get an ordered list of applied migrations to the database.

Expand All @@ -169,8 +169,10 @@ def get_applied_migrations(self) -> List[Migration]:
= coalesce($project,'<default>')
AND coalesce(m.migrationTarget,'<default>')
= coalesce($migration_target,'<default>')
WITH m,
[x IN split(m.version, '.') | toInteger(x)] AS version
RETURN m
ORDER BY m.version
ORDER BY version
""",
baseline=self.baseline,
project=self.project,
Expand Down
3 changes: 1 addition & 2 deletions neo4j_python_migrations/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from importlib.util import module_from_spec, spec_from_file_location
from itertools import chain
from pathlib import Path
from typing import List

from neo4j_python_migrations.migration import (
CypherMigration,
Expand All @@ -15,7 +14,7 @@
)


def load(path: Path) -> List[Migration]: # noqa: WPS210
def load(path: Path) -> list[Migration]: # noqa: WPS210
"""
Load local migrations that are stored at the specified path.

Expand Down
24 changes: 12 additions & 12 deletions neo4j_python_migrations/migration.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import binascii
from dataclasses import asdict, dataclass, field
from enum import Enum
from typing import Any, Callable, Dict, List, Optional
from typing import Any, Callable, Optional

from attr import asdict, define, field
from neo4j import Transaction
from packaging.version import Version

Expand All @@ -14,7 +14,7 @@ class MigrationType(str, Enum): # noqa: WPS600
CYPHER = "CYPHER"


@define(kw_only=True, order=False)
@dataclass(kw_only=True, order=False)
class Migration:
"""The base class for all migrations."""

Expand All @@ -26,7 +26,7 @@ class Migration:
checksum: Optional[str] = None

@classmethod
def from_dict(cls, properties: Dict[str, Any]) -> "Migration":
def from_dict(cls, properties: dict[str, Any]) -> "Migration":
"""
Get a class instance from a dictionary.

Expand Down Expand Up @@ -60,14 +60,14 @@ def apply(self, tx: Transaction) -> None:
"""
raise NotImplementedError()

def __attrs_post_init__(self) -> None:
def __post_init__(self) -> None:
self.parsed_version = Version(self.version) # noqa: WPS601

def __lt__(self, other: Any) -> bool:
return self.parsed_version < other.parsed_version


@define
@dataclass
class PythonMigration(Migration):
"""Migration based on a python code."""

Expand All @@ -78,19 +78,19 @@ def apply(self, tx: Transaction) -> None: # noqa: D102
self.code(tx)


@define
@dataclass
class CypherMigration(Migration):
"""Migration based on a cypher script."""

query: str = field(repr=False)
type: str = field(default=MigrationType.CYPHER, init=False)
statements: List[str] = field(init=False, repr=False)
statements: list[str] = field(init=False, repr=False)

def __attrs_post_init__(self) -> None:
super().__attrs_post_init__()
def __post_init__(self) -> None:
super().__post_init__()
self.statements = list( # noqa: WPS601
filter(
lambda statement: statement,
None,
[statement.strip() for statement in self.query.split(";")[:-1]],
),
)
Expand All @@ -99,7 +99,7 @@ def __attrs_post_init__(self) -> None:
for st in self.statements:
binary_statement = st.encode()
checksum = (
binascii.crc32(binary_statement, checksum) # type: ignore
binascii.crc32(binary_statement, checksum)
if checksum
else binascii.crc32(binary_statement)
)
Expand Down
Loading