Skip to content

Commit

Permalink
Merge pull request #53 from peopledoc/issue_47_use_version_object_eve…
Browse files Browse the repository at this point in the history
…rywhere

Use Version objects all the way up
  • Loading branch information
samuelhamard authored Jan 31, 2020
2 parents 976bc01 + 7671a47 commit 1e98054
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 117 deletions.
12 changes: 8 additions & 4 deletions septentrion/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
exceptions,
migrate,
style,
utils,
versions,
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -66,9 +66,13 @@ def load_config(ctx: click.Context, param: click.Parameter, value: TextIO) -> No


def validate_version(ctx: click.Context, param: Any, value: str):
if value and not utils.is_version(value):
if value is None:
return None
try:
version = versions.Version.from_string(value)
except exceptions.InvalidVersion:
raise click.BadParameter(f"{value} is not a valid version")
return value
return version


class CommaSeparatedMultipleString(StringParamType):
Expand Down Expand Up @@ -222,7 +226,7 @@ def migrate_func(settings: configuration.Settings):
@cli.command()
@click.argument("version", callback=validate_version)
@click.pass_obj
def fake(settings: configuration.Settings, version: str):
def fake(settings: configuration.Settings, version: versions.Version):
"""
Fake migrations until version.
Write migrations in the migration table without applying them, for
Expand Down
24 changes: 14 additions & 10 deletions septentrion/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

from typing import Any, Dict, Iterable, Optional

from septentrion import configuration, db, exceptions, files, style, utils
from septentrion import configuration, db, exceptions, files, style, utils, versions


def get_applied_versions(settings: configuration.Settings) -> Iterable[str]:
def get_applied_versions(
settings: configuration.Settings,
) -> Iterable[versions.Version]:
"""
Return the list of applied versions.
Reuse django migration table.
Expand All @@ -17,18 +19,18 @@ def get_applied_versions(settings: configuration.Settings) -> Iterable[str]:

known_versions = set(files.get_known_versions(settings=settings))

return utils.sort_versions(applied_versions & known_versions)
return sorted(applied_versions & known_versions)


# TODO: Refactor: this should just work with version numbers, not sql_tpl and
# not force_version
def get_closest_version(
settings: configuration.Settings,
target_version: str,
target_version: versions.Version,
sql_tpl: str,
existing_files: Iterable[str],
force_version: Optional[str] = None,
):
force_version: Optional[versions.Version] = None,
) -> Optional[versions.Version]:
"""
Get the version of a file (schema or fixtures) to use to init a DB.
Take the closest to the target_version. Can be the same version, or older.
Expand All @@ -53,15 +55,15 @@ def get_closest_version(
"settings.SCHEMA_VERSION is more recent."
)

file = sql_tpl.format(force_version)
file = sql_tpl.format(force_version.original_string)
if file in existing_files:
return force_version

# not found
return None

for version in previous_versions[::-1]:
schema_file = sql_tpl.format(version)
schema_file = sql_tpl.format(version.original_string)
if schema_file in existing_files:
return version

Expand All @@ -71,7 +73,7 @@ def get_closest_version(

# TODO: refactor this and the function below
# TODO: also remove files.get_special_files, it's not really useful
def get_best_schema_version(settings: configuration.Settings) -> str:
def get_best_schema_version(settings: configuration.Settings) -> versions.Version:
"""
Get the best candidate to init the DB.
"""
Expand All @@ -91,7 +93,9 @@ def get_best_schema_version(settings: configuration.Settings) -> str:
return version


def get_fixtures_version(settings: configuration.Settings, target_version: str) -> str:
def get_fixtures_version(
settings: configuration.Settings, target_version: versions.Version
) -> versions.Version:
"""
Get the closest fixtures to use to init a new DB
to the current target version.
Expand Down
26 changes: 17 additions & 9 deletions septentrion/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from psycopg2.extensions import connection as Connection
from psycopg2.extras import DictCursor

from septentrion import configuration, utils
from septentrion import configuration, versions

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -113,23 +113,29 @@ def __call__(self):
"""


def get_current_schema_version(settings: configuration.Settings) -> Optional[str]:
def get_current_schema_version(
settings: configuration.Settings,
) -> Optional[versions.Version]:
versions = get_applied_versions(settings=settings)
if not versions:
return None
return utils.get_max_version(versions)
return max(versions)


def get_applied_versions(settings: configuration.Settings) -> Iterable[str]:
def get_applied_versions(
settings: configuration.Settings,
) -> Iterable[versions.Version]:
with Query(settings=settings, query=query_max_version) as cur:
return [row[0] for row in cur]
return [versions.Version.from_string(row[0]) for row in cur]


def get_applied_migrations(
settings: configuration.Settings, version: str
settings: configuration.Settings, version: versions.Version
) -> Iterable[str]:
with Query(
settings=settings, query=query_get_applied_migrations, args=(version,)
settings=settings,
query=query_get_applied_migrations,
args=(version.original_string,),
) as cur:
return [row[0] for row in cur]

Expand All @@ -146,10 +152,12 @@ def create_table(settings: configuration.Settings) -> None:
Query(settings=settings, query=query_create_table, commit=True)()


def write_migration(settings: configuration.Settings, version: str, name: str) -> None:
def write_migration(
settings: configuration.Settings, version: versions.Version, name: str
) -> None:
Query(
settings=settings,
query=query_write_migration,
args=(version, name),
args=(version.original_string, name),
commit=True,
)()
14 changes: 9 additions & 5 deletions septentrion/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pathlib
from typing import Dict, Iterable, List, Tuple

from septentrion import configuration, exceptions, utils
from septentrion import configuration, exceptions, utils, versions


def iter_dirs(root: pathlib.Path) -> Iterable[pathlib.Path]:
Expand All @@ -23,7 +23,7 @@ def iter_files(
yield f


def get_known_versions(settings: configuration.Settings) -> Iterable[str]:
def get_known_versions(settings: configuration.Settings) -> Iterable[versions.Version]:
"""
Return the list of the known versions defined in migration repository,
ordered.
Expand All @@ -37,7 +37,11 @@ def get_known_versions(settings: configuration.Settings) -> Iterable[str]:
"settings.MIGRATIONS_ROOT is improperly configured."
)

return utils.sort_versions(name for name in folders_names if utils.is_version(name))
return sorted(
versions.Version.from_string(name)
for name in folders_names
if utils.is_version(name)
)


def is_manual_migration(
Expand Down Expand Up @@ -66,7 +70,7 @@ def get_special_files(root: pathlib.Path, folder: str) -> List[str]:


def get_migrations_files_mapping(
settings: configuration.Settings, version: str
settings: configuration.Settings, version: versions.Version
) -> Dict[str, pathlib.Path]:
"""
Return an dict containing the list of migrations for
Expand All @@ -76,7 +80,7 @@ def get_migrations_files_mapping(
"""
ignore_symlinks = settings.IGNORE_SYMLINKS

version_root = settings.MIGRATIONS_ROOT / version
version_root = settings.MIGRATIONS_ROOT / version.original_string
migrations = {}

# TODO: should be a setting
Expand Down
20 changes: 15 additions & 5 deletions septentrion/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
import logging
import pathlib

from septentrion import configuration, core, db, exceptions, files, runner, style, utils
from septentrion import (
configuration,
core,
db,
exceptions,
files,
runner,
style,
utils,
versions,
)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -53,7 +63,7 @@ def migrate(

def init_schema(
settings: configuration.Settings,
init_version: str,
init_version: versions.Version,
stylist: style.Stylist = style.noop_stylist,
) -> None:
# load additional files
Expand All @@ -73,7 +83,7 @@ def init_schema(
schema_path = (
settings.MIGRATIONS_ROOT
/ "schemas"
/ settings.SCHEMA_TEMPLATE.format(init_version)
/ settings.SCHEMA_TEMPLATE.format(init_version.original_string)
)

logger.info("Loading %s", schema_path)
Expand All @@ -95,7 +105,7 @@ def init_schema(
fixtures_path = (
settings.MIGRATIONS_ROOT
/ "fixtures"
/ settings.FIXTURES_TEMPLATE.format(fixtures_version)
/ settings.FIXTURES_TEMPLATE.format(fixtures_version.original_string)
)

with stylist.activate("title") as echo:
Expand All @@ -112,7 +122,7 @@ def init_schema(

def create_fake_entries(
settings: configuration.Settings,
version: str,
version: versions.Version,
stylist: style.Stylist = style.noop_stylist,
) -> None:
"""
Expand Down
21 changes: 1 addition & 20 deletions septentrion/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,6 @@
from septentrion import exceptions, versions


def sort_versions(iterable: Iterable[str]) -> Iterable[str]:
"""
Sorts an iterable of strings by increasing
equivalent version number
>>> sort_versions(["1.0.1", "2.0", "1.0.3"])
["1.0.1", "1.0.3", "2.0"]
"""
return sorted(iterable, key=versions.Version)


def get_max_version(iterable: Iterable[str]) -> str:
"""
Returns the latest version in the input iterable
>>> get_max_version(["1.3", "1.2"])
"1.3"
"""
return max(iterable, key=versions.Version)


def is_version(vstring: str) -> bool:
"""
Returns True if vstring is a valid version descriptor
Expand All @@ -35,7 +16,7 @@ def is_version(vstring: str) -> bool:
False
"""
try:
versions.Version(vstring)
versions.Version.from_string(vstring)
except exceptions.InvalidVersion:
return False
return True
Expand Down
27 changes: 11 additions & 16 deletions septentrion/versions.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import dataclasses

from septentrion import exceptions


@dataclasses.dataclass(order=True, unsafe_hash=True, frozen=True)
class Version:
def __init__(self, version_string: str):

version_tuple: tuple = dataclasses.field(compare=True)
original_string: str = dataclasses.field(compare=False)

@classmethod
def from_string(cls, version_string: str) -> "Version":
try:
assert version_string
self._version = tuple(int(e) for e in version_string.split("."))
version_tuple = tuple(int(e) for e in version_string.split("."))
except (AssertionError, ValueError) as exc:
raise exceptions.InvalidVersion(str(exc)) from exc

def __lt__(self, other: object) -> bool:
if isinstance(other, Version):
return self._version < other._version
raise NotImplementedError

def __eq__(self, other: object) -> bool:
if isinstance(other, Version):
return self._version == other._version
raise NotImplementedError

def __repr__(self) -> str:
version_string = ".".join(str(n) for n in self._version)
return f'{self.__class__.__name__}("{version_string}")'
return cls(version_tuple=version_tuple, original_string=version_string)
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ install_requires =
sqlparse
colorama
importlib-metadata
dataclasses; python_version <= "3.6"

[options.packages.find]
include =
Expand Down
4 changes: 3 additions & 1 deletion tests/acceptance/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,6 @@ def test_current_database_state(cli_runner, db):
)
assert result.exit_code == 0
assert db_module.is_schema_initialized(settings=settings)
assert db_module.get_current_schema_version(settings=settings) == "1.1"
assert (
db_module.get_current_schema_version(settings=settings).original_string == "1.1"
)
Loading

0 comments on commit 1e98054

Please sign in to comment.