23 Commits

Author SHA1 Message Date
  Ryan Reed 4039656986 Fix: Loading transpose config when none exists 1 year ago
  Ryan Reed cc3d7b1906 Updating version 1 year ago
  Ryan Reed 81ca92121d Fix: Check field is allowed before updating 1 year ago
  Ryan Reed 112db3e542 Adding upgrade script and removing unnecessary checks 1 year ago
  Ryan Reed c45ed47bb7 Removing unused changelog 1 year ago
  Ryan Reed afe2611332 Removing unused logger 1 year ago
  Ryan Reed d62840f5fb v2.2.0 - Enable, Disable Entries and Rework Config Updates 1 year ago
  Ryan Reed d748fae1e6 Updating version 1 year ago
  Ryan Reed 210d7a2941 Adding support for disabling and enabling specific entries in the config 1 year ago
  Ryan Reed 1200dc3cdb Reworking 'config update' to allow more general entry updates 1 year ago
  Ryan Reed 6a7e61889f Minor cleanup 1 year ago
  Ryan Reed de5c34b2be Add apply-all console command 1 year ago
  Ryan Reed 215f719458 Updating version 1 year ago
  Ryan Reed f72ead4fe6 Tests: Adding tests for console apply-all command 1 year ago
  Ryan Reed 7a58647b4b Exanduser when moving paths 1 year ago
  Ryan Reed 62012bfacf Move existing source paths to backup even if symlink 1 year ago
  Ryan Reed 3eada4349a Minor comment updates 1 year ago
  Ryan Reed 1d87679066 Add support for 'apply-all' console command 1 year ago
  Ryan Reed 31234aee01 Add support for created date in entries 1 year ago
  Ryan Reed 71f99363f7 Updating version 1 year ago
  Ryan Reed 3e84d37e80 Minor formatting and args rename 1 year ago
  Ryan Reed 064dbda16a Tests: Adding tests for console config commands 1 year ago
  Ryan Reed b520ea8bf9 Cleanup: Typos, comments, error message, and debugs 1 year ago
12 changed files with 423 additions and 95 deletions
Unified View
  1. +0
    -40
      CHANGELOG.md
  2. +6
    -3
      README.md
  3. +1
    -1
      pyproject.toml
  4. +26
    -0
      scripts/upgrade-2.2.py
  5. +0
    -4
      src/transpose/__init__.py
  6. +81
    -6
      src/transpose/console.py
  7. +0
    -6
      src/transpose/logger.py
  8. +78
    -19
      src/transpose/transpose.py
  9. +1
    -1
      src/transpose/utils.py
  10. +149
    -1
      tests/test_console.py
  11. +58
    -8
      tests/test_transpose.py
  12. +23
    -6
      tests/utils.py

+ 0
- 40
CHANGELOG.md View File

@ -1,40 +0,0 @@
# Change Log
## 1.1.0 (2022-07-26)
### Breaking Changes
* Moving store_path to only the store action
Note: I know I said this shouldn't happen again but I couldn't help myself. This time....for sure.
### Features
* Allow for short -s for --store-path
### Documentation
* Add Quick Reference section
### Tests
* Updating tests to support moving store_path to store action
## 1.0.2 (2022-07-17)
### Added
* The `name` argument is now optional when using the `transpose store`
### Changed
* Moved `name` argument to end of `transpose store`
Note: I'm abusing the versioning a bit as I'm the only user of this tool. Normally, this would be considered a breaking change due to the change in argument order. Shouldn't happen again.
## 1.0.1 (2022-07-16)
### Fixed
* Utilize `expanduser` and `~` in cache files to allow for more portable restorations
## 1.0.0 (2022-07-12)
Initial release

+ 6
- 3
README.md View File

@ -14,7 +14,7 @@ A tool for moving and symlinking directories to a central location
* [Storing a Directory](#storing-a-directory) * [Storing a Directory](#storing-a-directory)
* [Restoring a Stored Directory](#restoring-a-stored-directory) * [Restoring a Stored Directory](#restoring-a-stored-directory)
* [Applying a Previously Transpose Managed Directory](#applying-a-previously-transpose-managed-directory) * [Applying a Previously Transpose Managed Directory](#applying-a-previously-transpose-managed-directory)
* [Modifying Tranpose Config Directly](#modifying-tranpose-config-directly)
* [Modifying Transpose Config Directly](#modifying-transpose-config-directly)
* [Development](#development) * [Development](#development)
<!-- vim-markdown-toc --> <!-- vim-markdown-toc -->
@ -107,15 +107,18 @@ transpose apply "Game1"
``` ```
### Modifying Tranpose Config Directly
### Modifying Transpose Config Directly
It's possible to modify the tranpose configuration file, `STORE_PATH/transpose.json`, using the console:
It's possible to modify the transpose configuration file, `STORE_PATH/transpose.json`, using the console:
``` ```
transpose config add "NewEntry" "/path/to/location" transpose config add "NewEntry" "/path/to/location"
transpose config get "NewEntry" transpose config get "NewEntry"
transpose config disable "NewEntry"
transpose config enable "NewEntry"
transpose config list transpose config list
transpose config remove "NewEntry" transpose config remove "NewEntry"
transpose config update "NewEntry" "path" "/path/to/new/location"
``` ```


+ 1
- 1
pyproject.toml View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "transpose" name = "transpose"
version = "2.0.1"
version = "2.2.2"
description = "Move and symlink a path to a central location" description = "Move and symlink a path to a central location"
authors = ["Ryan Reed"] authors = ["Ryan Reed"]
license = "GPLv3" license = "GPLv3"


+ 26
- 0
scripts/upgrade-2.2.py View File

@ -0,0 +1,26 @@
"""
Loop through entries and ensure using the latest 2.2 entities
This means adding the following new fields to each entry:
* created (2.1)
* enabled (2.2)
"""
import json
from transpose import DEFAULT_STORE_PATH, TransposeConfig
def main() -> None:
config_file = f"{DEFAULT_STORE_PATH}/transpose.json"
with open(config_file, "r") as f:
d = json.load(f)
config = TransposeConfig()
for entry_name in d["entries"]:
config.add(entry_name, d["entries"][entry_name]["path"])
config.save(config_file)
if __name__ == "__main__":
main()

+ 0
- 4
src/transpose/__init__.py View File

@ -2,14 +2,10 @@ import os
from importlib.metadata import version from importlib.metadata import version
from .logger import create_logger
DEFAULT_XDG_PATH = os.environ.get("XDG_DATA_HOME", f"{os.environ['HOME']}/.local/share") DEFAULT_XDG_PATH = os.environ.get("XDG_DATA_HOME", f"{os.environ['HOME']}/.local/share")
STORE_PATH = f"{DEFAULT_XDG_PATH}/transpose" STORE_PATH = f"{DEFAULT_XDG_PATH}/transpose"
DEFAULT_STORE_PATH = os.environ.get("TRANSPOSE_STORE_PATH", STORE_PATH) DEFAULT_STORE_PATH = os.environ.get("TRANSPOSE_STORE_PATH", STORE_PATH)
version = version("transpose") version = version("transpose")
logger = create_logger(__package__)
from .transpose import Transpose, TransposeConfig, TransposeEntry # noqa: E402 from .transpose import Transpose, TransposeConfig, TransposeEntry # noqa: E402

+ 81
- 6
src/transpose/console.py View File

@ -13,7 +13,7 @@ def entry_point() -> None:
try: try:
run(args, config_path) run(args, config_path)
except TransposeError as e: except TransposeError as e:
print(f"Tranpose Error: {e}")
print(f"Transpose Error: {e}")
def run(args, config_path) -> None: def run(args, config_path) -> None:
@ -21,6 +21,8 @@ def run(args, config_path) -> None:
if args.action == "apply": if args.action == "apply":
t.apply(args.name, force=args.force) t.apply(args.name, force=args.force)
elif args.action == "apply-all":
run_apply_all(t, force=args.force)
elif args.action == "restore": elif args.action == "restore":
t.restore(args.name, force=args.force) t.restore(args.name, force=args.force)
elif args.action == "store": elif args.action == "store":
@ -32,6 +34,12 @@ def run(args, config_path) -> None:
if args.config_action == "add": if args.config_action == "add":
t.config.add(args.name, args.path) t.config.add(args.name, args.path)
t.config.save(config_path) t.config.save(config_path)
elif args.config_action == "disable":
t.config.disable(args.name)
t.config.save(config_path)
elif args.config_action == "enable":
t.config.enable(args.name)
t.config.save(config_path)
elif args.config_action == "get": elif args.config_action == "get":
print(t.config.get(args.name)) print(t.config.get(args.name))
elif args.config_action == "list": elif args.config_action == "list":
@ -41,10 +49,31 @@ def run(args, config_path) -> None:
t.config.remove(args.name) t.config.remove(args.name)
t.config.save(config_path) t.config.save(config_path)
elif args.config_action == "update": elif args.config_action == "update":
t.config.update(args.name, args.path)
t.config.update(args.name, args.field_key, args.field_value)
t.config.save(config_path) t.config.save(config_path)
def run_apply_all(t: Transpose, force: bool = False) -> None:
"""
Loop over the entries and recreate the symlinks to the store location
Useful after restoring a machine
Args:
t: An instance of Transpose
force: If enabled and path already exists, move the path to '{path}.backup' first
Returns:
None
"""
for entry_name in sorted(t.config.entries):
try:
t.apply(entry_name, force)
print(f"\t{entry_name:<30}: success")
except TransposeError as e:
print(f"\t{entry_name:<30}: {e}")
def parse_arguments(args=None): def parse_arguments(args=None):
base_parser = argparse.ArgumentParser(add_help=False) base_parser = argparse.ArgumentParser(add_help=False)
@ -77,7 +106,24 @@ def parse_arguments(args=None):
"name", "name",
help="The name of the stored entity to apply", help="The name of the stored entity to apply",
) )
apply_parser.add_argument("--force", dest="force", action="store_true")
apply_parser.add_argument(
"--force",
dest="force",
help="If original path already exists, existing path to <path>.backup and continue",
action="store_true",
)
apply_all_parser = subparsers.add_parser(
"apply-all",
help="Recreate the symlink for all entities",
parents=[base_parser],
)
apply_all_parser.add_argument(
"--force",
dest="force",
help="Continue with apply even if original path already exists or entry is disabled in config",
action="store_true",
)
restore_parser = subparsers.add_parser( restore_parser = subparsers.add_parser(
"restore", "restore",
@ -88,7 +134,12 @@ def parse_arguments(args=None):
"name", "name",
help="The name of the stored entity to restore", help="The name of the stored entity to restore",
) )
restore_parser.add_argument("--force", dest="force", action="store_true")
restore_parser.add_argument(
"--force",
dest="force",
help="Continue with restore even if original path already exists or entry is disabled in config",
action="store_true",
)
store_parser = subparsers.add_parser( store_parser = subparsers.add_parser(
"store", "store",
@ -129,6 +180,26 @@ def parse_arguments(args=None):
help="The path of the directory that should be symlinked to the store", help="The path of the directory that should be symlinked to the store",
) )
config_disable_parser = config_subparsers.add_parser(
"disable",
help="Disable an entry within the config",
parents=[base_parser],
)
config_disable_parser.add_argument(
"name",
help="The name of the entry the config",
)
config_enable_parser = config_subparsers.add_parser(
"enable",
help="enable an entry within the config",
parents=[base_parser],
)
config_enable_parser.add_argument(
"name",
help="The name of the entry the config",
)
config_get_parser = config_subparsers.add_parser( config_get_parser = config_subparsers.add_parser(
"get", "get",
help="Retrieve the settings of a specific entity, such as the path", help="Retrieve the settings of a specific entity, such as the path",
@ -165,8 +236,12 @@ def parse_arguments(args=None):
help="The name of the entry in the store path", help="The name of the entry in the store path",
) )
config_update_parser.add_argument( config_update_parser.add_argument(
"path",
help="The path of the directory that should be symlinked to the store",
"field_key",
help="The config key to be updated",
)
config_update_parser.add_argument(
"field_value",
help="The value to updated in the config",
) )
return parser.parse_args(args) return parser.parse_args(args)


+ 0
- 6
src/transpose/logger.py View File

@ -1,6 +0,0 @@
import logging
def create_logger(logger_name: str) -> logging:
logger = logging.getLogger(logger_name)
return logger

+ 78
- 19
src/transpose/transpose.py View File

@ -1,19 +1,23 @@
from dataclasses import asdict, dataclass, field from dataclasses import asdict, dataclass, field
from pathlib import Path from pathlib import Path
from typing import Any
# from typing import Self # from typing import Self
import datetime
import json import json
from . import version as transpose_version from . import version as transpose_version
from .exceptions import TransposeError from .exceptions import TransposeError
from .utils import move, remove, symlink
from .utils import move, symlink
@dataclass @dataclass
class TransposeEntry: class TransposeEntry:
name: str name: str
path: str path: str
created: str # Should be datetime.datetime but not really necessary here
enabled: bool = True
@dataclass @dataclass
@ -21,13 +25,14 @@ class TransposeConfig:
entries: dict = field(default_factory=dict) entries: dict = field(default_factory=dict)
version: str = field(default=transpose_version) version: str = field(default=transpose_version)
def add(self, name: str, path: str) -> None:
def add(self, name: str, path: str, created: str = None) -> None:
""" """
Add a new entry to the entries Add a new entry to the entries
Args: Args:
name: The name of the entry (must not exist) name: The name of the entry (must not exist)
path: The path where the entry originally exists path: The path where the entry originally exists
created: The date in datetime.now().__str__() format
Returns: Returns:
None None
@ -35,7 +40,44 @@ class TransposeConfig:
if self.entries.get(name): if self.entries.get(name):
raise TransposeError(f"'{name}' already exists") raise TransposeError(f"'{name}' already exists")
self.entries[name] = TransposeEntry(name=name, path=str(path))
if not created:
created = str(datetime.datetime.now())
self.entries[name] = TransposeEntry(
name=name,
path=str(path),
created=created,
)
def disable(self, name: str) -> None:
"""
Disable an entry by name. This ensures actions are not run against this entry, such as apply and restore
Args:
name: The name of the entry (must exist)
Returns:
None
"""
try:
self.entries[name].enabled = False
except KeyError:
raise TransposeError(f"'{name}' does not exist in Transpose config entries")
def enable(self, name: str) -> None:
"""
Enable an entry by name
Args:
name: The name of the entry (must exist)
Returns:
None
"""
try:
self.entries[name].enabled = True
except KeyError:
raise TransposeError(f"'{name}' does not exist in Transpose config entries")
def get(self, name: str) -> TransposeEntry: def get(self, name: str) -> TransposeEntry:
""" """
@ -67,33 +109,46 @@ class TransposeConfig:
except KeyError: except KeyError:
raise TransposeError(f"'{name}' does not exist in Transpose config entries") raise TransposeError(f"'{name}' does not exist in Transpose config entries")
def update(self, name: str, path: str) -> None:
def update(self, name: str, field_key: str, field_value: Any) -> None:
""" """
Update an entry by name
Update an entry's field (attribute) value
Args: Args:
name: The name of the entry (must exist) name: The name of the entry (must exist)
path: The path where the entry originally exists
field_key: The key to update
field_value: The value to update
Returns: Returns:
None None
""" """
try: try:
self.entries[name].path = path
if not hasattr(self.entries[name], field_key):
raise TransposeError(f"Unknown TransposeEntry field: {field_key}")
except KeyError: except KeyError:
raise TransposeError(f"'{name}' does not exist in Transpose config entries") raise TransposeError(f"'{name}' does not exist in Transpose config entries")
setattr(self.entries[name], field_key, field_value)
@staticmethod @staticmethod
def load(config_path: str): # -> Self: def load(config_path: str): # -> Self:
try: try:
in_config = json.load(open(config_path, "r")) in_config = json.load(open(config_path, "r"))
except json.decoder.JSONDecodeError as e: except json.decoder.JSONDecodeError as e:
raise TransposeError(f"Invalid JSON format for '{config_path}': {e}") raise TransposeError(f"Invalid JSON format for '{config_path}': {e}")
except FileNotFoundError:
in_config = {"entries": {}}
config = TransposeConfig() config = TransposeConfig()
try: try:
for name in in_config["entries"]: for name in in_config["entries"]:
config.add(name, in_config["entries"][name]["path"])
entry = in_config["entries"][name]
config.add(
name,
entry["path"],
created=entry["created"],
)
if not entry["enabled"]:
config.disable(name)
except (KeyError, TypeError) as e: except (KeyError, TypeError) as e:
raise TransposeError(f"Unrecognized Transpose config file format: {e}") raise TransposeError(f"Unrecognized Transpose config file format: {e}")
@ -113,7 +168,7 @@ class TransposeConfig:
config_path.parent.mkdir(parents=True, exist_ok=True) config_path.parent.mkdir(parents=True, exist_ok=True)
with open(str(config_path), "w") as f: with open(str(config_path), "w") as f:
json.dump(self.to_dict(), f)
json.dump(self.to_dict(), f, default=str)
def to_dict(self) -> dict: def to_dict(self) -> dict:
return asdict(self) return asdict(self)
@ -138,7 +193,7 @@ class Transpose:
Args: Args:
name: The name of the entry (must exist) name: The name of the entry (must exist)
force: If enabled and path already exists, move the path to '{path}-bak'
force: If enabled and path already exists, move the path to '{path}.backup' first
Returns: Returns:
None None
@ -146,11 +201,13 @@ class Transpose:
if not self.config.entries.get(name): if not self.config.entries.get(name):
raise TransposeError(f"Entry does not exist: '{name}'") raise TransposeError(f"Entry does not exist: '{name}'")
entry_path = Path(self.config.entries[name].path)
entry = self.config.entries[name]
if not entry.enabled and not force:
raise TransposeError(f"Entry '{name}' is not enabled in the config")
entry_path = Path(entry.path)
if entry_path.exists(): if entry_path.exists():
if entry_path.is_symlink():
remove(entry_path)
elif force: # Backup the existing path
if force: # Backup the existing path
move(entry_path, entry_path.with_suffix(".backup")) move(entry_path, entry_path.with_suffix(".backup"))
else: else:
raise TransposeError( raise TransposeError(
@ -168,7 +225,7 @@ class Transpose:
Args: Args:
name: The name of the entry (must exist) name: The name of the entry (must exist)
force: If enabled and path already exists, move the path to '{path}-bak'
force: If enabled and path already exists, move the path to '{path}.backup' first
Returns: Returns:
None None
@ -176,11 +233,13 @@ class Transpose:
if not self.config.entries.get(name): if not self.config.entries.get(name):
raise TransposeError(f"Could not locate entry by name: '{name}'") raise TransposeError(f"Could not locate entry by name: '{name}'")
entry_path = Path(self.config.entries[name].path)
entry = self.config.entries[name]
if not entry.enabled and not force:
raise TransposeError(f"Entry '{name}' is not enabled in the config")
entry_path = Path(entry.path)
if entry_path.exists(): if entry_path.exists():
if entry_path.is_symlink():
remove(entry_path)
elif force: # Backup the existing path
if force: # Backup the existing path
move(entry_path, entry_path.with_suffix(".backup")) move(entry_path, entry_path.with_suffix(".backup"))
else: else:
raise TransposeError( raise TransposeError(


+ 1
- 1
src/transpose/utils.py View File

@ -7,7 +7,7 @@ def move(source: Path, destination: Path) -> None:
""" """
Move a file using pathlib Move a file using pathlib
""" """
shutil.move(source, destination)
shutil.move(source.expanduser(), destination.expanduser())
def remove(path: Path) -> None: def remove(path: Path) -> None:


+ 149
- 1
tests/test_console.py View File

@ -1,6 +1,42 @@
import pytest import pytest
from transpose.console import parse_arguments
from pathlib import Path
from transpose import TransposeConfig
from transpose.console import parse_arguments, run as run_console
from .utils import (
setup_restore,
setup_apply,
ENTRY_NAME,
SECOND_ENTRY_NAME,
STORE_PATH,
TARGET_PATH,
SECOND_TARGET_PATH,
TRANSPOSE_CONFIG_PATH,
)
class RunActionArgs:
name: str = ENTRY_NAME
path: str = str(TARGET_PATH)
action: str
force: bool
def __init__(self, action: str, force: bool = False) -> None:
self.action = action
self.force = force
class RunConfigArgs:
name: str = ENTRY_NAME
action: str = "config"
force: bool = False
path: str = str(TARGET_PATH)
config_action: str
def __init__(self, config_action: str) -> None:
self.config_action = config_action
def test_parse_arguments(): def test_parse_arguments():
@ -96,3 +132,115 @@ def test_parse_arguments_restore():
args = parse_arguments(["restore", "SomeName", "--force"]) args = parse_arguments(["restore", "SomeName", "--force"])
assert args.force is True assert args.force is True
@setup_apply()
def test_run_apply():
args = RunActionArgs("apply", False)
run_console(args, TRANSPOSE_CONFIG_PATH)
assert TARGET_PATH.is_symlink()
@setup_apply()
def test_run_apply_all(capsys):
args = RunActionArgs("apply-all", False)
run_console(args, TRANSPOSE_CONFIG_PATH)
captured = capsys.readouterr()
assert f"\t{ENTRY_NAME:<30}: success" in captured.out
assert f"\t{SECOND_ENTRY_NAME:<30}: Entry path already exists" in captured.out
assert TARGET_PATH.is_symlink()
assert SECOND_TARGET_PATH.is_dir()
args.force = True
run_console(args, TRANSPOSE_CONFIG_PATH)
captured = capsys.readouterr()
assert f"\t{ENTRY_NAME:<30}: success" in captured.out
assert f"\t{SECOND_ENTRY_NAME:<30}: success" in captured.out
assert SECOND_TARGET_PATH.is_symlink()
assert SECOND_TARGET_PATH.with_suffix(".backup").is_dir()
def test_run_restore():
pass
def test_run_store():
pass
@setup_restore()
def test_run_config_add():
args = RunConfigArgs("add")
args.name = "MyName2"
run_console(args, TRANSPOSE_CONFIG_PATH)
config = TransposeConfig().load(TRANSPOSE_CONFIG_PATH)
assert config.entries.get(args.name)
@setup_restore()
def test_run_config_disable():
args = RunConfigArgs("disable")
run_console(args, TRANSPOSE_CONFIG_PATH)
config = TransposeConfig().load(TRANSPOSE_CONFIG_PATH)
assert config.entries[args.name].enabled is False
@setup_restore()
def test_run_config_enable():
args = RunConfigArgs("enable")
run_console(args, TRANSPOSE_CONFIG_PATH)
config = TransposeConfig().load(TRANSPOSE_CONFIG_PATH)
assert config.entries[args.name].enabled is True
@setup_restore()
def test_run_config_get(capsys):
args = RunConfigArgs("get")
run_console(args, TRANSPOSE_CONFIG_PATH)
captured = capsys.readouterr()
assert str(TARGET_PATH) in captured.out
@setup_restore()
def test_run_config_list(capsys):
args = RunConfigArgs("list")
run_console(args, TRANSPOSE_CONFIG_PATH)
captured = capsys.readouterr()
assert f"-> {TARGET_PATH}" in captured.out
@setup_restore()
def test_run_config_remove():
args = RunConfigArgs("remove")
run_console(args, TRANSPOSE_CONFIG_PATH)
config = TransposeConfig().load(TRANSPOSE_CONFIG_PATH)
assert not config.entries.get(args.name)
@setup_restore()
def test_run_config_update():
args = RunConfigArgs("update")
args.field_key = "path"
args.field_value = "/var/tmp/something"
run_console(args, TRANSPOSE_CONFIG_PATH)
config = TransposeConfig().load(TRANSPOSE_CONFIG_PATH)
assert config.entries[args.name].path == args.field_value

+ 58
- 8
tests/test_transpose.py View File

@ -38,12 +38,6 @@ def test_apply():
with pytest.raises(TransposeError, match="Entry does not exist"): with pytest.raises(TransposeError, match="Entry does not exist"):
t.apply("BadName") t.apply("BadName")
# Will remove the symlink created above and reapply
# TODO: Check symlink path
t.apply(ENTRY_NAME)
assert TARGET_PATH.is_symlink()
assert ENTRY_STORE_PATH.is_dir()
# Target already exists, force not set # Target already exists, force not set
TARGET_PATH.unlink() TARGET_PATH.unlink()
TARGET_PATH.mkdir() TARGET_PATH.mkdir()
@ -58,11 +52,26 @@ def test_apply():
assert TARGET_PATH.is_symlink() assert TARGET_PATH.is_symlink()
assert ENTRY_STORE_PATH.is_dir() assert ENTRY_STORE_PATH.is_dir()
# Target is disabled in the config
t.config.entries[ENTRY_NAME].enabled = False
with pytest.raises(
TransposeError, match=f"Entry '{ENTRY_NAME}' is not enabled in the config"
):
t.apply(ENTRY_NAME)
@setup_restore() @setup_restore()
def test_restore(): def test_restore():
t = Transpose(config_path=TRANSPOSE_CONFIG_PATH) t = Transpose(config_path=TRANSPOSE_CONFIG_PATH)
# Target is disabled in the config
t.config.entries[ENTRY_NAME].enabled = False
with pytest.raises(
TransposeError, match=f"Entry '{ENTRY_NAME}' is not enabled in the config"
):
t.restore(ENTRY_NAME)
t.config.entries[ENTRY_NAME].enabled = True
# Success # Success
t.restore(ENTRY_NAME) t.restore(ENTRY_NAME)
assert TARGET_PATH.is_dir() assert TARGET_PATH.is_dir()
@ -131,6 +140,28 @@ def test_config_add():
assert config.entries["NewEntry"].path == str(TARGET_PATH) assert config.entries["NewEntry"].path == str(TARGET_PATH)
@setup_store()
def test_config_disable():
config = TransposeConfig.load(TRANSPOSE_CONFIG_PATH)
with pytest.raises(TransposeError, match="'UnknownEntry' does not exist"):
config.disable("UnknownEntry")
config.disable(ENTRY_NAME)
assert config.entries[ENTRY_NAME].enabled is False
@setup_store()
def test_config_enable():
config = TransposeConfig.load(TRANSPOSE_CONFIG_PATH)
with pytest.raises(TransposeError, match="'UnknownEntry' does not exist"):
config.enable("UnknownEntry")
config.enable(ENTRY_NAME)
assert config.entries[ENTRY_NAME].enabled is True
@setup_store() @setup_store()
def test_config_get(): def test_config_get():
config = TransposeConfig.load(TRANSPOSE_CONFIG_PATH) config = TransposeConfig.load(TRANSPOSE_CONFIG_PATH)
@ -163,9 +194,12 @@ def test_config_update():
with pytest.raises( with pytest.raises(
TransposeError, match="does not exist in Transpose config entries" TransposeError, match="does not exist in Transpose config entries"
): ):
config.update("UnknownEntry", "/some/new/path")
config.update("UnknownEntry", "path", "/some/new/path")
config.update(ENTRY_NAME, "/some/new/path")
with pytest.raises(TransposeError, match="Unknown TransposeEntry field"):
config.update(ENTRY_NAME, "UnknownField", "Some Value")
config.update(ENTRY_NAME, "path", "/some/new/path")
assert config.entries[ENTRY_NAME].path == "/some/new/path" assert config.entries[ENTRY_NAME].path == "/some/new/path"
@ -182,6 +216,22 @@ def test_config_save():
) )
@setup_store()
def test_config_save_fresh():
"""
Verify creation of transpose config when doesn't initially exist
"""
TRANSPOSE_CONFIG_PATH.unlink()
config = TransposeConfig.load(TRANSPOSE_CONFIG_PATH)
assert len(config.entries) == 0
config.add("TestEntry", TARGET_PATH)
config.save(TRANSPOSE_CONFIG_PATH)
config = TransposeConfig.load(TRANSPOSE_CONFIG_PATH)
assert config.entries.get("TestEntry")
@setup_store() @setup_store()
def test_config_load(): def test_config_load():
config = TransposeConfig.load(TRANSPOSE_CONFIG_PATH) config = TransposeConfig.load(TRANSPOSE_CONFIG_PATH)


+ 23
- 6
tests/utils.py View File

@ -9,9 +9,11 @@ from transpose import version
ENTRY_NAME = "MyName" ENTRY_NAME = "MyName"
SECOND_ENTRY_NAME = "SecondEntry"
TESTS_PATH = Path("tests-temp") TESTS_PATH = Path("tests-temp")
STORE_PATH = TESTS_PATH.joinpath("store") STORE_PATH = TESTS_PATH.joinpath("store")
TARGET_PATH = TESTS_PATH.joinpath("source") TARGET_PATH = TESTS_PATH.joinpath("source")
SECOND_TARGET_PATH = TESTS_PATH.joinpath("second_source")
SYMLINK_TEST_PATH = TESTS_PATH.joinpath("symlink_test") SYMLINK_TEST_PATH = TESTS_PATH.joinpath("symlink_test")
ENTRY_STORE_PATH = STORE_PATH.joinpath(ENTRY_NAME) ENTRY_STORE_PATH = STORE_PATH.joinpath(ENTRY_NAME)
@ -19,7 +21,20 @@ TRANSPOSE_CONFIG_PATH = STORE_PATH.joinpath("transpose.json")
TRANSPOSE_CONFIG = { TRANSPOSE_CONFIG = {
"version": version, "version": version,
"entries": {ENTRY_NAME: {"name": ENTRY_NAME, "path": str(TARGET_PATH)}},
"entries": {
ENTRY_NAME: {
"name": ENTRY_NAME,
"path": str(TARGET_PATH),
"created": "2023-01-21 01:02:03.1234567",
"enabled": True,
},
SECOND_ENTRY_NAME: {
"name": SECOND_ENTRY_NAME,
"path": str(SECOND_TARGET_PATH),
"created": "2023-02-23 01:02:03.1234567",
"enabled": True,
},
},
} }
@ -30,13 +45,17 @@ def setup_apply():
tests-temp/ tests-temp/
store/ store/
transpose.json transpose.json
MyName/
MyName/
SecondEntry/
second_source/
symlink_test/ -> source/ symlink_test/ -> source/
""" """
try: try:
with TemporaryDirectory(str(TESTS_PATH)): with TemporaryDirectory(str(TESTS_PATH)):
STORE_PATH.mkdir(parents=True, exist_ok=True) STORE_PATH.mkdir(parents=True, exist_ok=True)
ENTRY_STORE_PATH.mkdir(parents=True, exist_ok=True) ENTRY_STORE_PATH.mkdir(parents=True, exist_ok=True)
STORE_PATH.joinpath(SECOND_ENTRY_NAME).mkdir()
SECOND_TARGET_PATH.mkdir()
SYMLINK_TEST_PATH.symlink_to(TARGET_PATH.resolve()) SYMLINK_TEST_PATH.symlink_to(TARGET_PATH.resolve())
with open(str(TRANSPOSE_CONFIG_PATH), "w") as f: with open(str(TRANSPOSE_CONFIG_PATH), "w") as f:
@ -79,8 +98,8 @@ def setup_store():
tests-temp/ tests-temp/
source/ source/
store/ store/
transpose-bad.json
transpose-invalid.json
transpose-bad.json
transpose-invalid.json
transpose.json transpose.json
""" """
try: try:
@ -95,13 +114,11 @@ def setup_store():
bad_config = '{"version": "1.0.0"}' # Missing entries bad_config = '{"version": "1.0.0"}' # Missing entries
with open(str(bad_config_path), "w") as f: with open(str(bad_config_path), "w") as f:
f.write(bad_config) f.write(bad_config)
assert bad_config_path.exists()
invalid_config_path = STORE_PATH.joinpath("transpose-invalid.json") invalid_config_path = STORE_PATH.joinpath("transpose-invalid.json")
invalid_config = "[{'invalidJSONFormat'}]" invalid_config = "[{'invalidJSONFormat'}]"
with open(str(invalid_config_path), "w") as f: with open(str(invalid_config_path), "w") as f:
f.write(invalid_config) f.write(invalid_config)
assert invalid_config_path.exists()
yield yield
finally: finally:


Loading…
Cancel
Save