Skip to content

Commit

Permalink
Merge pull request #44 from tim-band/config-release-docs
Browse files Browse the repository at this point in the history
Config release docs
  • Loading branch information
tim-band authored Jan 29, 2024
2 parents 5287607 + a98a2bc commit 9a49b9e
Show file tree
Hide file tree
Showing 29 changed files with 809 additions and 201 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,28 @@ and notify when manual verification should be rerun due to changes in requiremen

## Installation

### Using pipx

Install the latest version from github:

```
pipx install git+https://github.com/UCL-ARC/qw
```

Install a particular version from github:

```
pipx install git+https://github.com/UCL-ARC/[email protected]
```

Install from the source code directory:

```
pipx install .
```

### Using venv

The `qw` tool requires python 3.9 or greater, if your project is in python then we suggest adding it to the developer requirements.
If not, you may want to create a virtual environment for your python, one option is the [venv](https://docs.python.org/3.9/library/venv.html) module.

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ requires-python = ">=3.11"
license.file = "LICENCE.md"

[project.scripts]
qw = "qw.cli:app"
qw = "qw.cli:run_app"

[project.urls]
homepage = "https://github.com/UCL-ARC/qw"
Expand Down Expand Up @@ -109,6 +109,7 @@ twine = "^4.0.2"

[tool.pytest.ini_options]
addopts = "--color=yes -v"
pythonpath = "src"
testpaths = [
"tests",
]
Expand Down
196 changes: 122 additions & 74 deletions src/qw/changes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Compares changes between remote and local data, allowing the user to make decisions."""
from collections import OrderedDict, defaultdict

from loguru import logger
from rich.console import Console
from rich.prompt import Confirm, Prompt
from rich.table import Table
Expand All @@ -14,43 +13,141 @@
class ChangeHandler:
"""Allow user interaction to manage changes between local and remote design stage data."""

class DiffElement:
"""A potential update to the local store."""

def __init__(
self,
service: GitService,
local_item: DesignStages | None,
remote_item: DesignStages | None,
):
"""
Initialize.
:param service: The Git service object.
:param local_item: The "self" (local) item of the diff.
:param remote_item: The "other" (remote) item of the diff.
"""
self._service = service
self._diff = None
if local_item is not None and remote_item is not None:
self._diff = local_item.diff(remote_item)
self._local_item = local_item
self._remote_item = remote_item

def show(self):
"""Show this difference on the screen."""
if not bool(self._diff):
return
table = Table(
title=f"Changes detected for {self._local_item}:",
show_lines=True,
expand=True,
)
table.add_column("Field", justify="right", style="cyan")
table.add_column("Local", justify="left", style="magenta")
table.add_column(
f"{self._service.username}/{self._service.reponame}",
justify="left",
style="green",
)
for field, differences in self._diff.items():
table.add_row(field, differences["self"], differences["other"])

Console().print(table)

def prompt_for_version_change(self):
"""
Prompt the user for what they want to do with this diff.
:return: The item so be stored in the local store; either
the local item (for no change) or the remote item (possibly
with the version number incremented).
"""
if not bool(self._diff):
if self._local_item is None:
# New remote item, no prompt required
return self._remote_item
if self._remote_item is not None:
# Both exist, but no difference
return self._local_item
# Only local exists, remote has been deleted
if Confirm.ask(
f"{self._local_item} no longer exists in remote, would you like to remove it from the local store?",
):
# Remove the local item
return None
# Keep the local item
return self._local_item
prompt = "\n".join(
[
"Would you like to:",
"n (Don't save the update)",
"u (Update, but trivial change so don't increment the version)",
"i (Update and increment the version)",
"",
],
)

response = Prompt.ask(prompt, choices=["n", "u", "i"])
if response == "n":
return self._local_item
if response == "i":
self._remote_item.version += 1
return self._remote_item

def __init__(self, service: GitService, store: LocalStore):
"""Create ChangeHandler instance."""
self._service = service
self._store = store

def combine_local_and_remote_items(self) -> list[DesignStages]:
"""Compare local and remote design stages and prompt on any differences."""
diff_elements = self.diff_remote_and_local_items()
return self.get_local_items_from_diffs(diff_elements)

def diff_remote_and_local_items(self) -> list[DiffElement]:
"""
Find all differences between local and remote design stages.
:return: A list of diff elements, each of which is an item
that differs between the local store and the remote service.
"""
paired = self._pair_remote_and_local()

output_items = []
diff_elements = []
for _internal_id, pair in paired.items():
remote_item: DesignStages | None = pair.get("remote")
local_item: DesignStages | None = pair.get("local")
if not local_item:
logger.info(
f"New remote item: {remote_item} will be saved to local store.",
)
output_items.append(remote_item)
continue

if not remote_item:
output_items.extend(self._prompt_to_remove_local_item(local_item))
continue

diff = local_item.diff(remote_item)
if not diff:
output_items.append(local_item)
continue

output_items.append(
self._prompt_for_version_change(
diff,
local_item,
remote_item,
diff_elements.append(
ChangeHandler.DiffElement(
self._service,
pair.get("local"),
pair.get("remote"),
),
)

return diff_elements

@classmethod
def get_local_items_from_diffs(
cls,
diff_elements: list[DiffElement],
) -> list[DesignStages]:
"""
Transform DiffElements into local items to be stored.
:param diff_elements: An iterable of DiffElements, each
representing a difference between the remote and local
items.
:return: A list of local items to be set in the store.
"""
output_items = []
for diff_element in diff_elements:
diff_element.show()
output_item = diff_element.prompt_for_version_change()
if output_item is not None:
output_items.append(output_item)

return output_items

def _pair_remote_and_local(self) -> dict[int, dict[str, DesignStages]]:
Expand All @@ -63,52 +160,3 @@ def _pair_remote_and_local(self) -> dict[int, dict[str, DesignStages]]:
paired_data[stage.internal_id]["local"] = stage

return OrderedDict(sorted(paired_data.items()))

@staticmethod
def _prompt_to_remove_local_item(local_item) -> list[DesignStages]:
if Confirm.ask(
f"{local_item} no longer exists in remote, would you like to remove it from the local store?",
):
return []
return [local_item]

def _prompt_for_version_change(
self,
diff: dict[str, dict],
local_item: DesignStages,
remote_item: DesignStages,
):
table = Table(
title=f"Changes detected for {local_item}:",
show_lines=True,
expand=True,
)
table.add_column("Field", justify="right", style="cyan")
table.add_column("Local", justify="left", style="magenta")
table.add_column(
f"{self._service.username}/{self._service.reponame}",
justify="left",
style="green",
)
for field, differences in diff.items():
table.add_row(field, differences["self"], differences["other"])

console = Console()
console.print(table)
prompt = "\n".join(
[
"Would you like to:",
"n (Don't save the update)",
"u (Update, but trivial change so don't increment the version)",
"i (Update and increment the version)",
"",
],
)

response = Prompt.ask(prompt, choices=["n", "u", "i"])
if response == "n":
return local_item
if response == "u":
return remote_item
remote_item.version += 1
return remote_item
Loading

0 comments on commit 9a49b9e

Please sign in to comment.